Beispiel #1
0
    def dailySumStyle(self, ws, tf, anchor=(1, 1)):
        '''
        Create the shape and populate the daily Summary Form
        :params:ws: The openpyxl Worksheet object
        :parmas:tf: The TradeFormat object. It holds the styles used.
        :params:listOfTrade: A python list of DataFrames, each one a trade with multiple tickets
        :params:anchor: The location of the top left corner of the form
        TODO: This is probably better placed in layoutSheet -- similar to
              LayoutSheet.populateMistakeForm() and using the Trade Summaries object instead of the
              trades object... may do that later, but now-- I'm going to finish this version --
              momentum and all.
        '''

        # Alter the anchor to place this form below the (dynamically sized) Mistake form
        anchor = (anchor[0], anchor[1] + self.numTrades + 5)

        for key in self.dailySummaryFields:
            rng = self.dailySummaryFields[key][0]
            style = self.dailySummaryFields[key][1]
            if isinstance(rng, list):
                tf.mergeStuff(ws, rng[0], rng[1], anchor=anchor)
                ws[tcell(rng[0], anchor=anchor)].style = tf.styles[style]
                mrng = tcell(rng[0], rng[1], anchor=anchor)
                style_range(ws, mrng, border=tf.styles[style].border)
                # if key in headers:
                if len(self.dailySummaryFields[key]) == 3:

                    ws[tcell(rng[0], anchor=anchor)] = self.dailySummaryFields[key][2]

            else:
                ws[tcell(rng, anchor=anchor)].style = tf.styles[style]
                # if key in headers:
                if len(self.dailySummaryFields[key]) == 3:
                    ws[tcell(rng, anchor=anchor)] = self.dailySummaryFields[key][2]
    def test_styleTopwithnothin(self):
        '''
        Test the method layoutsheet.LayoutSheet.styleTop. Test that it still works without
        table data. We still know too much about the method,. Note that we are using a protected
        member of Worksheet ws._tables
        '''
        quoteRange = [(1, 1), (13, 5)]
        noteRange = [(1, 6), (13, 24)]
        quoteStyle = 'normStyle'
        noteStyle = 'explain'
        margin = 25
        inputlen = 50   #len(df)

        wb = Workbook()
        ws = wb.active

        # Make sure the out dir exists
        if not os.path.exists("out/"):
            os.mkdir("out/")

        # Make sure the file we are about to create does not exist
        dispath = "out/SCHNOrK.xlsx"
        if os.path.exists(dispath):
            os.remove(dispath)

        ls = LayoutSheet(margin, inputlen)
        ls.styleTop(ws, 13, TradeFormat(wb))

        wb.save(dispath)

        wb2 = load_workbook(dispath)
        ws2 = wb2.active

        listOfMerged = list()
        listOfMerged.append(tcell((quoteRange[0])) + ':' +  tcell((quoteRange[1])))
        listOfMerged.append(tcell((noteRange[0])) + ':' +  tcell((noteRange[1])))
        for xx in ws2.merged_cells.ranges:
            # print (str(xx) in listOfMerged)
            self.assertIn(str(xx), listOfMerged)
        self.assertEqual(ws[tcell(quoteRange[0])].style, quoteStyle)
        self.assertEqual(ws[tcell(noteRange[0])].style, noteStyle)

        self.assertEqual(len(ws._tables), 1)


        begin = tcell((1, ls.topMargin))

        end = tcell((13, ls.topMargin + ls.inputlen))
        tabRange = f'{begin}:{end}'
        self.assertEqual(tabRange, ws._tables[0].ref)

        os.remove(dispath)
    def test_dailySumStyle(self):
        '''
        Test the method MistakeSummary.mstkSumStyle.
        '''
        wb = Workbook()
        ws = wb.active
        tf = TradeFormat(wb)

        numTrades = 5

        ms = MistakeSummary(numTrades)

        ms.dailySumStyle(ws, tf)

        dispath = "out/SCHNOrK.xlsx"
        if os.path.exists(dispath):
            os.remove(dispath)

        wb.save(dispath)

        anchor = (1, ms.anchor[1] + ms.numTrades + 5)

        wb2 = load_workbook(dispath)
        ws2 = wb2.active

        # test that each listed item in ms.mistakeFields has a corresponding style set in the
        # appropriate cell in the re-opened workbook.
        for x in ms.dailySummaryFields:
            cell = ''
            entry = ms.dailySummaryFields[x]
            if isinstance(entry[0], list):
                cell = tcell(entry[0][0], anchor=anchor)
            else:
                cell = tcell(entry[0], anchor=anchor)
            self.assertEqual(entry[1], ws2[cell].style)

            # Get the entries with the static values-- the headers
            if len(entry) == 3:
                self.assertEqual(ws2[cell].value, ms.dailySummaryFields[x][2])
        os.remove(dispath)
    def test_mstkSumStyles(self):
        '''
        Test the method MistakeSummary.mstkSumStyle.
        '''
        wb = Workbook()
        ws = wb.active
        tf = TradeFormat(wb)

        ms = MistakeSummary(5)

        ms.mstkSumStyle(ws, tf)

        dispath = "out/SCHNOrK.xlsx"
        if os.path.exists(dispath):
            os.remove(dispath)

        wb.save(dispath)

        wb2 = load_workbook(dispath)
        ws2 = wb2.active

        # test that each listed item in ms.mistakeFields has a corresponding style set in the
        # appropriate cell in the re-opened workbook.
        for x in ms.mistakeFields:
            cell = ''
            entry = ms.mistakeFields[x]
            if isinstance(entry[0], list):
                cell = tcell(entry[0][0])
            else:
                cell = tcell(entry[0])

            # print(cell, entry[1], ws[cell].style)
            self.assertEqual(entry[1], ws[cell].style)

            if len(entry) == 3:
                self.assertEqual(ws2[cell].value, ms.mistakeFields[x][2])
        os.remove(dispath)
Beispiel #5
0
    def mstkSumStyle(self, ws, tf, anchor=(1, 1)):
        '''
        Create the shape and stye for the Mistake summary form, populate the static values.
        The rest is done in layoutsheet including formulas (with cell translation) and the
        names, each with a hyperinks to the Trade Summary form.
        :params ws: The openpyx worksheet object
        :params tf: The TradeFormat object which has the styles
        :params anchor: The cell value at the Top left of the form in tuple form.
        '''
        a = anchor

        # Merge the cells, apply the styles, and populate the fields we can--the
        # fields that don't know any details todays trades (other than how many trades)
        # That includes the non-formula fields and the sum formula below
        for key in self.mistakeFields:
            rng = self.mistakeFields[key][0]
            style = self.mistakeFields[key][1]

            if isinstance(self.mistakeFields[key][0], list):
                tf.mergeStuff(ws, rng[0], rng[1], anchor=a)
                ws[tcell(rng[0], anchor=a)].style = tf.styles[style]
                mrng = tcell(rng[0], rng[1], anchor=a)
                style_range(ws, mrng, border=tf.styles[style].border)
                if len(self.mistakeFields[key]) == 3:
                    ws[tcell(rng[0], anchor=a)] = self.mistakeFields[key][2]

            else:
                ws[tcell(rng, anchor=a)].style = tf.styles[style]
                if len(self.mistakeFields[key]) == 3:
                    # ws[tcell(rng, anchor=a)] = headers[key]
                    ws[tcell(rng, anchor=a)] = self.mistakeFields[key][2]

        # The total sum formula is done here. It is self contained to references to the Mistake
        # Summary form
        totcell = self.mistakeFields['total'][0]
        begincell = (totcell[0], totcell[1] - self.numTrades)
        endcell = (totcell[0], totcell[1] - 1)
        rng = tcell(begincell, endcell, anchor=a)
        totcell = tcell(totcell, anchor=a)
        f = '=SUM({0})'.format(rng)
        ws[totcell] = f
Beispiel #6
0
def loadTradeSummaries(loc, trades):
    '''
    Load up each trade summary in the excel doc into a 1 row DataFrame, return a list
    of these DataFrames
    :params:loc: A list of the rows within the excel doc on which to find the trade summaries
    :return: A list of 1-row DataFrames Each trade is on one row from each of the trade summay forms
    '''

    ldf = list()
    srf = SumReqFields()
    reqCol = srf.rc
    newdf = pd.DataFrame(columns=reqCol.values())
    colFormat = srf.tfcolumns

    for rowNum in loc:
        newdf = DataFrameUtil.createDf(newdf, 1)
        for key in reqCol.keys():
            cell = colFormat[reqCol[key]][0]
            if isinstance(cell, list):
                cell = cell[0]
            cell = tcell(cell, anchor=(1, rowNum))
            newdf.iloc[-1][reqCol[key]] = trades[cell].value
        ldf.append(newdf)
    return ldf
Beispiel #7
0
def registerTrades(tsList, wb):
    for fname, theDate in tsList:
        print(fname, theDate)
        wb2 = load_workbook(fname)
        trades = wb2["Sheet"]

        tradeLocation = getTradeSummaryFormLocations(trades)

        ldf = loadTradeSummaries(tradeLocation, trades)
        drc = DisReqCol(theDate)

        tlog = wb["Trade Log"]

        startSearch = False
        ix = -2
        cols = drc.tfcolumns
        # Here is a list of the keys to use cols.keys() of the trade log DataFrame
        #['date', 'time', 'side', 'symb', 'entry1', 'acctbal', 'shares',
        #'stoploss', 'targ', 'avgexit', 'pl', 'notes'])
        # Not bothering with the abstraction (drc.name) because this is entirely ours.

        srf = SumReqFields()

        for row in tlog.iter_rows():
            anchor = (1, row[0].row)

            if startSearch == True:
                if not row[0].value:
                    startSearch = False
                    ix = 0
            if row[0].value == 'Date':
                startSearch = True

            if ix >= 0:
                tdf = ldf[ix]
                if not gotAnyExits(tdf):
                    continue
                # print(cols.keys())

                #date
                cell = tcell(cols['date'][0], anchor=anchor)
                tlog[cell] = theDate

                #time
                cell = tcell(cols['time'][0], anchor=anchor)
                tim = tdf[srf.start].unique()[0]
                tlog[cell] = tim

                #side
                name = tdf[srf.name].unique()[0]
                if name:
                    cell = tcell(cols['side'][0], anchor=anchor)
                    tlog[cell] = name.split()[1]

                #symb
                cell = tcell(cols['symb'][0], anchor=anchor)
                tlog[cell] = name.split()[0]

                #entry1
                cell = tcell(cols['entry1'][0], anchor=anchor)
                tlog[cell] = tdf[srf.entry1].unique()[0]

                #Account Balance (setting an excel formula)
                cell = tcell(cols['acctbal'][0], anchor=anchor)
                formula = "=$M$3+SUM($N$7:$N{})".format(row[0].row - 1)
                tlog[cell] = formula

                # "shares"
                cell = tcell(cols['shares'][0], anchor=anchor)
                shares = tdf[srf.shares].unique()[0].split()[0]
                if len(shares) > 0:
                    try:
                        ishares = int(shares)
                    except:
                        ishares = 0
                tlog[cell] = ishares

                #stoploss
                cell = tcell(cols['stoploss'][0], anchor=anchor)
                sl = tdf[srf.stoploss].unique()[0]
                tlog[cell] = sl

                #target
                cell = tcell(cols['targ'][0], anchor=anchor)
                target = tdf[srf.targ].unique()[0]
                tlog[cell] = target

                #avgExit
                cell = tcell(cols['avgexit'][0], anchor=anchor)
                tlog[cell] = getAvgExit(tdf)

                # P/L
                cell = tcell(cols['pl'][0], anchor=anchor)
                pl = tdf[srf.pl].unique()[0]
                tlog[cell] = pl

                # Strategy
                cell = tcell(cols['strat'][0], anchor=anchor)
                strat = tdf[srf.strat].unique()[0]
                tlog[cell] = strat

                # notes (from the mistake note field)
                cell = tcell(cols['notes'][0], anchor=anchor)
                note = tdf[srf.mstknote].unique()[0]
                tlog[cell] = note

                ix = ix + 1
                if ix == len(ldf):
                    break
    def test_runSummaries(self, unusedstub1, unusedstub2, unusedstub3):
        '''
        Test the method prunSummaries. The setup here is alost the entire module trade.py
        We run the standard set of infiles
        '''

        # global D
        # infiles = ['trades.1116_messedUpTradeSummary10.csv', 'trades.8.WithHolds.csv',
        #         'trades.8.csv', 'trades.907.WithChangingHolds.csv',
        #         'trades_190117_HoldError.csv', 'trades.8.ExcelEdited.csv',
        #         'trades.910.tickets.csv', 'trades_tuesday_1121_DivBy0_bug.csv',
        #         'trades.8.WithBothHolds.csv', 'trades1105HoldShortEnd.csv',
        #         'trades190221.BHoldPreExit.csv']

        global D
        for tdata, infile in zip(self.dadata, self.infiles):
            # :::::::::  Setup   ::::::::

            D = deque(tdata)
            # :::::::::::::: SETUP ::::::::::::::
            # theDate = '2018-11-05'
            outdir = 'out/'
            indir = 'C:/python/E/structjour/src/data/'
            mydevel = False
            jf = JournalFiles(infile=infile, outdir=outdir, indir=indir, mydevel=mydevel)


            trades, jf = Statement_DAS(jf).getTrades()
            trades, success = InputDataFrame().processInputFile(trades)
            inputlen, dframe, ldf = DefineTrades().processOutputDframe(trades)

            # Process the openpyxl excel object using the output file DataFrame. Insert
            # images and Trade Summaries.
            margin = 25

            # Create the space in dframe to add the summary information for each trade.
            # Then create the Workbook.
            ls = LayoutSheet(margin, inputlen)
            imageLocation, dframe = ls.imageData(dframe, ldf)
            wb, ws, dummy = ls.createWorkbook(dframe)

            tf = TradeFormat(wb)

            mstkAnchor = (len(dframe.columns) + 2, 1)
            mistake = MistakeSummary(numTrades=len(ldf), anchor=mstkAnchor)
            mistake.mstkSumStyle(ws, tf, mstkAnchor)

            # :::::::::::::: END SETUP ::::::::::::::
            tradeSummaries = ls.runSummaries(imageLocation, ldf, jf, ws, tf)

            # Make sure the out dir exists
            if not os.path.exists("out/"):
                os.mkdir("out/")

            # Make sure the file we are about to create does not exist
            dispath = "out/SCHNOrK.xlsx"
            if os.path.exists(dispath):
                os.remove(dispath)
            wb.save(dispath)

            wb2 = load_workbook(dispath)
            ws2 = wb2.active


            srf = SumReqFields()
            for trade, loc in zip(tradeSummaries, imageLocation):
                anchor = (1, loc[0])
                # print(anchor)
                for col in trade:
                    if col in['clean']:
                        continue

                    # Get the cell
                    if isinstance(srf.tfcolumns[col][0], list):
                        cell = tcell(srf.tfcolumns[col][0][0], anchor=anchor)
                    else:
                        cell = tcell(srf.tfcolumns[col][0], anchor=anchor)

                    # Nicer to read
                    wsval = ws2[cell].value
                    tval = trade[col].unique()[0]

                    # Test Formulas (mostly skipping for now because its gnarly)
                    # Formulas in srf.tfformulas including the translation stuff

                    if col in srf.tfformulas.keys():
                        self.assertTrue(wsval.startswith('='))

                    # Test empty cells
                    elif not tval:
                        # print(wsval, '<------->', tval)
                        self.assertIs(wsval, None)

                    # Test floats
                    elif isinstance(tval, float):
                        # print(wsval, '<------->', tval)
                        self.assertAlmostEqual(wsval, tval)

                    # Time vals
                    elif isinstance(tval, (pd.Timestamp, dt.datetime, np.datetime64)):
                        wsval = pd.Timestamp(wsval)
                        tval = pd.Timestamp(tval)
                        self.assertGreaterEqual((wsval-tval).total_seconds(), -.01)
                        self.assertLessEqual((wsval-tval).total_seconds(), .01)


                    # Test everything else
                    else:
                        # print(wsval, '<------->', tval)
                        self.assertEqual(wsval, tval)
    def test_populateDailySummaryForm(self, unusedstub1, unusedstub2, unusedstub3):
        '''
        Test the method populateMistakeForm. The setup here is alost the entire module trade.py
        The tested method puts in the trade PL and notes
        '''

        global D


        for tdata, infile in zip(self.dadata, self.infiles):
            # :::::::::  Setup   ::::::::
            D = deque(tdata)
            # :::::::::::::: SETUP ::::::::::::::
            # theDate = '2018-11-05'
            outdir = 'out/'
            indir = 'data/'
            mydevel = False
            jf = JournalFiles(infile=infile, outdir=outdir, indir=indir, mydevel=mydevel)
            print(jf.inpathfile)

            trades, jf = Statement_DAS(jf).getTrades()
            trades, success = InputDataFrame().processInputFile(trades)
            inputlen, dframe, ldf = DefineTrades().processOutputDframe(trades)

            # Process the openpyxl excel object using the output file DataFrame. Insert
            # images and Trade Summaries.
            margin = 25

            # Create the space in dframe to add the summary information for each trade.
            # Then create the Workbook.
            ls = LayoutSheet(margin, inputlen)
            imageLocation, dframe = ls.imageData(dframe, ldf)
            wb, ws, dummy = ls.createWorkbook(dframe)

            tf = TradeFormat(wb)

            mstkAnchor = (len(dframe.columns) + 2, 1)
            mistake = MistakeSummary(numTrades=len(ldf), anchor=mstkAnchor)
            # mistake.mstkSumStyle(ws, tf, mstkAnchor)
            mistake.dailySumStyle(ws, tf, mstkAnchor)
            tradeSummaries = ls.runSummaries(imageLocation, ldf, jf, ws, tf)

            # :::::::::::::: END SETUP ::::::::::::::
            # ls.populateMistakeForm(tradeSummaries, mistake, ws, imageLocation)
            ls.populateDailySummaryForm(tradeSummaries, mistake, ws, mstkAnchor)

            # Make sure the out dir exists
            if not os.path.exists("out/"):
                os.mkdir("out/")

            # Make sure the file we are about to create does not exist
            dispath = "out/SCHNOrK.xlsx"
            if os.path.exists(dispath):
                os.remove(dispath)

            wb.save(dispath)
            # print(infile, 'saved as', dispath)

            wb2 = load_workbook(dispath)
            ws2 = wb2.active

            # Live Total
            frc = FinReqCol()
            livetot = 0
            simtot = 0
            highest = 0
            lowest = 0
            numwins = 0
            numlosses = 0
            totwins = 0
            totloss = 0

            for trade in tradeSummaries:
                acct = trade[frc.acct].unique()[0]
                pl = trade[frc.PL].unique()[0]
                highest = pl if pl > highest else highest
                lowest = pl if pl < lowest else lowest
                if pl > 0:
                    numwins += 1
                    totwins += pl
                # Trades == 0 are figured in the loss column-- comissions and all
                else:
                    numlosses += 1
                    totloss += pl


                if acct == 'Live':
                    livetot += pl
                elif acct == 'SIM':
                    simtot += pl

            # print(livetot)
            # livetotcell = tcell(mistake.dailySummaryFields['livetot'][0], anchor=ls.DSFAnchor)
            # print(simtot)

            avgwin = 0 if numwins == 0 else totwins/numwins
            avgloss = 0 if numlosses == 0 else totloss/numlosses

            data = [['livetot', livetot], ['simtot', simtot], ['highest', highest],
                    ['lowest', lowest], ['avgwin', avgwin], ['avgloss', avgloss]]

            for s, d in data:
                cell = tcell(mistake.dailySummaryFields[s][0], anchor=ls.DSFAnchor)
                # msg = '{} {} {}'.format(s, d, ws2[cell].value)
                # print(msg)
                self.assertAlmostEqual(d, ws2[cell].value)  #, abs_tol=1e-7)


            data = ['livetotnote', 'simtotnote', 'highestnote', 'lowestnote',
                    'avgwinnote', 'avglossnote']
            for s in data:
                cell = tcell(mistake.dailySummaryFields[s][0][0], anchor=ls.DSFAnchor)
                val = ws2[cell].value
                self.assertIsInstance(val, str)

                self.assertGreater(len(val), 1)
Beispiel #10
0
    def test_populateMistakeForm(self, unusedstub1, unusedstub2, unusedstub3):
        '''
        Test the method populateMistakeForm. The setup here is alost the entire module trade.py
        '''

        global D
        for tdata, infile in zip(self.dadata, self.infiles):
            # :::::::::  Setup   ::::::::
            D = deque(tdata)
            # :::::::::::::: SETUP ::::::::::::::
            # theDate = '2018-11-05'
            outdir = 'out/'
            indir = 'data/'
            mydevel = False
            jf = JournalFiles(infile=infile, outdir=outdir, indir=indir, mydevel=mydevel)

            trades, jf = Statement_DAS(jf).getTrades()
            trades, success = InputDataFrame().processInputFile(trades)
            inputlen, dframe, ldf = DefineTrades().processOutputDframe(trades)

            # Process the openpyxl excel object using the output file DataFrame. Insert
            # images and Trade Summaries.
            margin = 25

            # Create the space in dframe to add the summary information for each trade.
            # Then create the Workbook.
            ls = LayoutSheet(margin, inputlen)
            imageLocation, dframe = ls.imageData(dframe, ldf)
            wb, ws, dummy = ls.createWorkbook(dframe)

            tf = TradeFormat(wb)

            mstkAnchor = (len(dframe.columns) + 2, 1)
            mistake = MistakeSummary(numTrades=len(ldf), anchor=mstkAnchor)
            mistake.mstkSumStyle(ws, tf, mstkAnchor)

            tradeSummaries = ls.runSummaries(imageLocation, ldf, jf, ws, tf)

            # :::::::::::::: END SETUP ::::::::::::::
            ls.populateMistakeForm(tradeSummaries, mistake, ws, imageLocation)

            # Make sure the out dir exists
            if not os.path.exists("out/"):
                os.mkdir("out/")

            # Make sure the file we are about to create does not exist
            dispath = "out/SCHNOrK.xlsx"
            if os.path.exists(dispath):
                os.remove(dispath)
            wb.save(dispath)

            wb2 = load_workbook(dispath)
            ws2 = wb2.active

            frc = FinReqCol()

            # ragged iteration over mistakeFields and tradeSummaries.
            count = 0   # ragged iterator for tradeSummaries
            for key in mistake.mistakeFields:

                entry = mistake.mistakeFields[key]
                cell = entry[0][0] if isinstance(entry[0], list) else entry[0]
                cell = tcell(cell, anchor=mistake.anchor)
                if key.startswith('name'):
                    # Get the hyperlink target in mistakeform , parse the target and verify the
                    # hyperlinks point to each other
                    tsName = tradeSummaries[count][frc.name].unique()[0]
                    tsAcct = tradeSummaries[count][frc.acct].unique()[0]
                    targetcell = ws2[cell].hyperlink.target.split('!')[1]
                    originalcell = ws2[targetcell].hyperlink.target.split('!')[1]

                    # print(ws2[cell].value, '<--------', tsumName)
                    # print(ws2[cell].value, '<-------', tsumAccount)
                    # print(cell, '<------->', originalcell)
                    self.assertGreater(ws2[cell].value.find(tsName), -1)
                    self.assertGreater(ws2[cell].value.find(tsAcct), -1)
                    self.assertEqual(cell, originalcell)
                    count = count + 1


            # ::::::: tpl fields :::::::
            count = 0
            for key in mistake.mistakeFields:

                entry = mistake.mistakeFields[key]
                cell = entry[0][0] if isinstance(entry[0], list) else entry[0]
                cell = tcell(cell, anchor=mistake.anchor)
                if key.startswith('tpl'):
                    targetcell = ws2[cell].value[1:]
                    origval = tradeSummaries[count][frc.PL].unique()[0]
                    # print(ws2[targetcell].value, '<------->', origval )
                    if origval == 0:
                        self.assertIs(ws2[targetcell].value, None)
                    else:
                        self.assertAlmostEqual(ws2[targetcell].value, origval)
                    count = count + 1

                # These next two tests (for plx and mistakex) have no unique entries (without user
                # input or mock) Test for the static values and that plx entry is next to its header
                if key.startswith('pl'):
                    headval = 'Proceeds Lost'
                    targetcell = ws2[cell].value[1:]
                    headercell = 'A' + targetcell[1:]
                    # print(ws2[targetcell].value, '<------->', None)
                    # print(headercell, '------->', ws2[headercell].value)
                    self.assertTrue(ws2[targetcell].value is None)
                    self.assertEqual(ws2[headercell].value, headval)

                if key.startswith('mistake'):
                    noteval = 'Final note'
                    targetcell = ws2[cell].value[1:]
                    # print(ws2[targetcell].value, '<------->', noteval)
                    self.assertEqual(ws2[targetcell].value, noteval)
Beispiel #11
0
    def test_styleTop(self):
        '''
        Test the method layoutsheet.LayoutSheet.styleTop. This  will probably produce warnings from
        openpyxl as there is empty data when it makes the headers. No worries.
        Note that we are using a protected member of Worksheet ws._tables, so if it this fails, look
        at that. openpyxl does not provide a public attribute for tables.
        Note that knowing the quoteRange and noteRange is bad design. Eventually these two bits of
        design data should be abstracted to somewhere accessible by the user. (and testing too)
        '''
        quoteRange = [(1, 1), (13, 5)]
        noteRange = [(1, 6), (13, 24)]
        quoteStyle = 'normStyle'
        noteStyle = 'explain'
        margin = 25
        inputlen = 50   #len(df)

        wb = Workbook()
        ws = wb.active

        # Make sure the out dir exists
        if not os.path.exists("out/"):
            os.mkdir("out/")

        # Make sure the file we are about to create does not exist
        dispath = "out/SCHNOrK.xlsx"
        if os.path.exists(dispath):
            os.remove(dispath)

        # Create table header and data in the ws
        headers = ['Its', 'the', 'end', 'of', 'the', 'world', 'as', 'we',
                   'know', 'it.', 'Bout', 'Fn', 'Time!']
        for i in range(1, 14):
            ws[tcell((i, 25))] = headers[i-1]

        ls = LayoutSheet(margin, inputlen)
        for x in range(ls.topMargin+1, ls.inputlen + ls.topMargin+1):
            for xx in range(1, 14):
                ws[tcell((xx, x))] = randint(-1000, 10000)


        ls.styleTop(ws, 13, TradeFormat(wb))

        wb.save(dispath)

        wb2 = load_workbook(dispath)
        ws2 = wb2.active

        listOfMerged = list()
        listOfMerged.append(tcell((quoteRange[0])) + ':' +  tcell((quoteRange[1])))
        listOfMerged.append(tcell((noteRange[0])) + ':' +  tcell((noteRange[1])))
        for xx in ws2.merged_cells.ranges:
            # print (str(xx) in listOfMerged)
            self.assertTrue(str(xx) in listOfMerged)
        self.assertEqual(ws[tcell(quoteRange[0])].style, quoteStyle)
        self.assertEqual(ws[tcell(noteRange[0])].style, noteStyle)

        self.assertEqual(len(ws._tables), 1)


        begin = tcell((1, ls.topMargin))

        end = tcell((13, ls.topMargin + ls.inputlen))
        tabRange = f'{begin}:{end}'
        self.assertEqual(tabRange, ws._tables[0].ref)

        os.remove(dispath)