# -*-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()