# -*-mode: python; py-indent-offset: 4; indent-tabs-mode: nil; encoding: utf-8-dos; coding: utf-8 -*- """ Give the Symbol Timeframe and Year to backtest The Timeframe is the period in minutes: e.g. 1 60 240 1440 """ from __future__ import print_function import sys, os import traceback from PandasMt4 import oPreprocessOhlc from Omlettes import Omlette """ Set of data-loading helpers """ # http://nbviewer.ipython.org/github/ematvey/pybacktest/blob/master/examples/tutorial.ipynb #? is this generic or specific to this chefModule? #? IOOW does oPyBacktestCook belong here or PybacktestChef.py? #? I think it belongs as a method to a class instance in PybacktestChef # replace ChefModule with the instance and make cook a method in it. # Thing is, I think dApplyRecipe is Chef dependent. def oPyBacktestCook(dFeeds, oRecipe, oChefModule, oOm, oFd=sys.stdout): """ Returns an error message string on failure; a Cooker instance on success. """ #? Why is dFeeds unused? from collections import OrderedDict dDishes = oRecipe.dApplyRecipe() rBuy = rCover = dDishes['rBuy'] rSell = rShort = dDishes['rSell'] if not len(rBuy[rBuy == True]): sMsg = "WARN: NO Buy/Cover signals; quitting!" sys.stderr.write(sMsg +'\n') return sMsg if not len(rSell[rSell == True]): sMsg = "WARN: NO Short/Sell signals; quitting!" sys.stderr.write(sMsg +'\n') return sMsg oFd.write('INFO: Buy/Cover signals: \n%s\n' % rBuy[rBuy == True]) oFd.write('INFO: Short/Sell signals: \n%s\n' % rSell[rSell == True]) mOhlc = oRecipe.dIngredients['mOhlc'] assert len(mOhlc) == len(rBuy) == len(rSell) #? is this generic or specific to this chefModule? #? IOOW does dMakeChefsParams belong in Omlette or PybacktestChef.py? #? I think it belongs in Omlette and happens to work in PybacktestChef dDataObj = oOm.dMakeChefsParams(buy=rBuy, sell=rSell, short=rShort, cover=rCover, ohlc=mOhlc) #? should we test the dDataObj in oChefModule for validity? # *dataobj* should be dict-like structure containing signal series. # *signal_fields* specifies names of signal Series that backtester will # attempt to extract from dataobj. # the keys of sSataObj must be in the list and order of signal_fields dChefParams = OrderedDict( # derive these from the series signal_fields=('buy', 'sell', 'short', 'cover'), open_label='O', close_label='C', ) oBt = oChefModule.ChefsOven(mOhlc, dDataObj, name=oOm.oRecipe.sName, **dChefParams) #? mOhlc oOm.vAppendHdf('recipe/dishes/rBuy', rBuy) oOm.vAppendHdf('recipe/dishes/rSell', rSell) oOm.vAppendHdf('recipe/dishes/rShort', rShort) oOm.vAppendHdf('recipe/dishes/rCover', rCover) # FixMe: dChefParams['sName'] = oOm.oChefModule.sChef dChefParams['sUrl'] = 'file://' +oChefModule.__file__ oOm.vSetMetadataHdf('recipe/dishes', dChefParams) return oBt def vPlotEquityCurves(oBt, mOhlc, oChefModule, sPeriod='W', close_label='C',): import matplotlib import matplotlib.pylab as pylab # FixMe: matplotlib.rcParams['figure.figsize'] = (10, 5) # FixMe: derive the period from the sTimeFrame oChefModule.vPlotEquity(oBt.equity, mOhlc, sTitle="%s\nEquity" % repr(oBt), sPeriod=sPeriod, close_label=close_label, ) pylab.show() oBt.vPlotTrades() pylab.legend(loc='lower left') pylab.show() ## oBt.vPlotTrades(subset=slice(sYear+'-05-01', sYear+'-09-01')) ## pylab.legend(loc='lower left') ## pylab.show() def oParseOptions(sUsage): """ usage: OTBackTest.py [-h] [-P] [-R SRECIPE] [-C SCHEF] [-O SOMELETTE] [lArgs [lArgs ...]] give the Symbol Timeframe and Year to backtest The Timeframe is the period in minutes: e.g. 1 60 240 1440 positional arguments: lArgs the Symbol Timeframe and Year to backtest (required) optional arguments: -h, --help show this help message and exit -P, --plot_equity plot the equity curves of the servings -R SRECIPE, --recipe SRECIPE recipe to backtest -C SCHEF, --chef SCHEF recipe to backtest -O SOMELETTE, --omelette SOMELETTE store the recipe and servings in an hdf5 store """ from argparse import ArgumentParser oArgParser = ArgumentParser(description=sUsage) oArgParser.add_argument('-P', '--plot_equity', dest='bPlotEquity', action='store_true', default=False, help='plot the equity curves of the servings') oArgParser.add_argument("-R", "--recipe", action="store", dest="sRecipe", default="SMARecipe", help="recipe to backtest") oArgParser.add_argument("-C", "--chef", action="store", dest="sChef", default="PybacktestChef", help="chef to cook") oArgParser.add_argument("-O", "--omelette", action="store", dest="sOmelette", default="", help="store the recipe and servings in an hdf5 store") oArgParser.add_argument('lArgs', action="store", nargs="*", help="the CsvFile Symbol Timeframe and Year to backtest") return oArgParser def oOmain(lArgv): # FixMe: logging oFd = sys.stdout oArgParser = oParseOptions(__doc__.strip()) oOptions = oArgParser.parse_args(lArgv) lArgs = oOptions.lArgs assert len(lArgs) == 4, "Give the CsvFile Symbol Timeframe and Year as arguments" #sDir = '/t/Program Files/HotForex MetaTrader/history/tools.fxdd.com' # sCsvFile = os.path.join(sDir, sSymbol + sTimeFrame +'-' +sYear +'.csv') sCsvFile = lArgs[0] sSymbol = lArgs[1] # 'EURGBP' sTimeFrame = lArgs[2] # '1440' sYear = lArgs[3] # '2014' # if not os.path.isfile(sCsvFile): vResampleFiles(sSymbol, sDir) assert os.path.isfile(sCsvFile) oOm = Omlette.Omlette(sHdfStore=oOptions.sOmelette, oFd=oFd) oRecipe = oOm.oAddRecipe(oOptions.sRecipe) oChefModule = oOm.oAddChef(oOptions.sChef) dFeedParams = dict(sTimeFrame=sTimeFrame, sSymbol=sSymbol, sYear=sYear) dFeed = oOm.dGetFeedFrame(sCsvFile, **dFeedParams) mFeedOhlc = dFeed['mFeedOhlc'] mFeedOhlc = oPreprocessOhlc(mFeedOhlc) oFd.write('INFO: Data Open length: %d\n' % len(mFeedOhlc)) # ugly - should be a list of different feed timeframes etc. dFeeds = dict(mFeedOhlc=mFeedOhlc, dFeedParams=dFeedParams) # dRecipeParams now comes from the recipe ini file: bUseTalib=oOptions.bUseTalib dIngredientsParams = dict(dRecipeParams=dict()) oRecipe.dMakeIngredients(dFeeds) assert oRecipe.dIngredients oBt = oPyBacktestCook(dFeeds, oRecipe, oChefModule, oOm) assert oBt is not None if isinstance(oBt, basestring): raise RuntimeError(oBt) # this was the same as: oBt._mSignals = bt.mSignals() or oBt.signals oBt._mSignals = oRecipe.mSignals(oBt) oFd.write('INFO: bt.signals found: %d\n' % len(oBt.signals)) oOm.vAppendHdf('recipe/servings/mSignals', oBt.signals) # this was the same as: oBt._mTrades = oBt.mTrades() or oBt.trades oBt._mTrades = oRecipe.mTrades(oBt) oFd.write('INFO: bt.trades found: %d\n' % len(oBt.trades)) oOm.vAppendHdf('recipe/servings/mTrades', oBt.trades) # this was the same as: oBt._rPositions = oBt.rPositions() or oBt.positions oBt._rPositions = oRecipe.rPositions(oBt, init_pos=0) oFd.write('INFO: bt.positions found: %d\n' % len(oBt.positions)) oOm.vAppendHdf('recipe/servings/rPositions', oBt.positions) # this was the same as: oBt._rEquity = oBt.rEquity() or oBt.equity oBt._rEquity = oRecipe.rEquity(oBt) oFd.write('INFO: bt.equity found: %d\n' % len(oBt.equity)) oOm.vAppendHdf('recipe/servings/rEquity', oBt.equity) # oFd.write('INFO: bt.rTradePrice() found: %d\n' % len(oBt.rTradePrice())) oFd.write('INFO: bt.trade_price found: %d\n' % len(oBt.trade_price)) oOm.vAppendHdf('recipe/servings/rTradePrice', oBt.trade_price) oOm.vSetTitleHdf('recipe/servings', oOm.oChefModule.sChef) #? Leave this as derived or store it? reviews? oOm.vSetMetadataHdf('recipe/servings', oBt.dSummary()) oFd.write(oBt.sSummary()) oOm.vSetMetadataHdf('recipe', dict(oRecipe.oConfig)) if oOptions.bPlotEquity: mOhlc = oRecipe.dIngredients['mOhlc'] vPlotEquityCurves(oBt, mOhlc, oOm.oChefModule) return oOm def iMain(): iRetval = 0 oOm = None try: oOm = oOmain(sys.argv[1:]) except KeyboardInterrupt: pass except Exception as e: sys.stderr.write("ERROR: " +str(e) +"\n" + \ traceback.format_exc(10) +"\n") sys.stderr.flush() sys.exc_clear() iRetval = 1 finally: if oOm: oOm.vClose() return iRetval if __name__ == '__main__': iMain()