def parse_ExecutionReport(self, message, sending_time):
        # Extract fields from the message here and pass to an upper layer
        if self.verbose:
            print('[fromApp] Execution Report received!')
            print(f'[fromApp] {read_FIX_message(message)}')

        # Tag 11 (client order ID, the one we sent)
        # must be the same type (int) as in open_orders.
        ClOrdID = extract_message_field_value(fix.ClOrdID(), message, 'int')
        # print('ClOrdID:', ClOrdID)

        # Tag 15
        # _Currency = extract_message_field_value(fix.Currency(), message)
        # print('_Currency:', _Currency)

        # Tag 17
        # _ExecID = extract_message_field_value(fix.ExecID(), message)
        # print('_ExecID:', _ExecID)

        # Tag 37
        # _OrderID = extract_message_field_value(fix.OrderID(), message)
        # print('_OrderID:', _OrderID)

        # Tag 39 OrderStatus: 0 = New, 1 = Partially filled, 2 = Filled, 3 = Done for day, 4 = Canceled,
        # 6 = Pending Cancel (e.g. result of Order Cancel Request <F>), 7 = Stopped,
        # 8 = Rejected, 9 = Suspended, A = Pending New, B = Calculated, C = Expired,
        # D = Accepted for bidding, E = Pending Replace (e.g. result of Order Cancel/Replace Request <G>)
        # maybe also check 3=Done for day, 7=Stopped, 9=Suspended, B=Calculated and C=Expired, but it seems that Stopped means it can still be filled?
        # https://www.onixs.biz/fix-dictionary/4.4/tagNum_39.html
        ordStatus = extract_message_field_value(fix.OrdStatus(), message,
                                                'str')
        # print('ordStatus:', ordStatus)

        # Tag 150 Execution type
        # 0 = New, 4 = Canceled, F = Trade (partial fill or fill), I = Order Status, ...
        _ExecType = extract_message_field_value(fix.ExecType(), message)
        # print('_ExecType:', _ExecType)

        # if the exection report is a response to an OrderStatusRequest,
        # fields other than OrdStatus might not be set.
        if _ExecType == 'I':
            if ClOrdID in self.open_orders.keys():
                self.open_orders[ClOrdID].status = ordStatus
            else:
                print(f'Order {ClOrdID} not found! Order status: {ordStatus}')
            return

        # Tag 40 OrderType: 1 = Market, 2 = Limit, 3 = Stop
        ordType = extract_message_field_value(fix.OrdType(), message, 'str')
        # print('ordType:', ordType)

        # Tag 44
        price = extract_message_field_value(fix.Price(), message, 'float')
        # print('price:', price)

        # Tag 54
        side = extract_message_field_value(fix.Side(), message, 'str')
        # print('side:', side)

        # Tag 55
        symbol = extract_message_field_value(fix.Symbol(), message, 'str')
        # print('symbol:', symbol)

        # canceled or rejected: here a few fields are not defined, which would
        # prevent further parsing of the message. therefore, exiting earlier.
        # https://www.onixs.biz/fix-dictionary/4.4/tagNum_39.html
        # tags not defined: 60, 18, 100
        # [fromApp] 8=FIX.4.4, 9=164, 35=8, 34=33, 49=XCD17, 52=20201020-10:04:25.518,
        # 56=T008, 11=51515, 14=0.0, 17=0, 37=0, 38=1000, 39=8, 40=2, 44=1.17, 54=1,
        # 55=EUR/USD, 58=reject: duplicate clOrdID, 150=8, 151=0.0, 10=073

        if ordStatus == '4' or ordStatus == '6' or ordStatus == '8':

            action = 'canceled'
            if ordStatus == '8':
                action = 'rejected'

            if ClOrdID in self.open_orders.keys(
            ):  # the report might be sent after restarting the FIX algo.
                if ordStatus == '4' or ordStatus == '6':
                    if self.verbose:
                        print(f'Order {action}: {self.open_orders[ClOrdID]}')
                if ordStatus == '8':

                    if self.verbose:
                        print(f'Order {action}: {self.open_orders[ClOrdID]}')
                del self.open_orders[ClOrdID]
            elif self.verbose:
                print(f'Order {action}, but not found in open_orders.')

            report = execution_report(ClOrdID, symbol, side, price, ordType,
                                      ordStatus, 0, 0, 0, 0)
            self.execution_history.append(report)

            if self.verbose:
                print(report)

            transactTime = datetime_to_str(datetime.datetime.utcnow())
            log(
                self.execution_logger,
                '{},{},{},{},{},{},{},{},{},{},{}'.format(
                    transactTime, ClOrdID, symbol, side, price, ordType,
                    ordStatus, 0, 0, 0, 0))

            if self.read_positions_from_file:
                self.save_positions_to_file()

            self.lock.acquire()
            self.tick_processor.on_execution_report(report, self)
            self.lock.release()
            return

        # Tag 60 (how to make it a datetime object?)
        # here without extract_message_field_value() because we want to call getString() and not getValue().
        transactTime = fix.TransactTime()
        message.getField(transactTime)
        transactTime = transactTime.getString()
        # print('transactTime:', transactTime)

        # Tag 18
        orderQty = extract_message_field_value(fix.OrderQty(), message, 'int')
        # print('orderQty:', orderQty)

        # Tag 110
        minQty = extract_message_field_value(fix.MinQty(), message, 'int')
        # print('minQty:', minQty)

        # Tag 14 CumQty: Total quantity filled.
        cumQty = extract_message_field_value(fix.CumQty(), message, 'int')
        # print('cumQty:', cumQty)

        # Tag 151 LeavesQty: Quantity open for further execution. 0 if 'Canceled', 'DoneForTheDay',
        # 'Expired', 'Calculated', or' Rejected', else LeavesQty <151> = OrderQty <38> - CumQty <14>.
        leavesQty = extract_message_field_value(fix.LeavesQty(), message,
                                                'int')
        # print('leavesQty:', leavesQty)

        if not ClOrdID in self.open_orders.keys():
            log(self.execution_logger,
                f'[ERROR] ClOrdID {ClOrdID} not found in open_orders:', True)
            for o in self.open_orders:
                log(self.execution_logger, o)
            report = execution_report(ClOrdID, symbol, side, price, ordType,
                                      ordStatus, orderQty, minQty, cumQty,
                                      leavesQty)
            self.execution_history.append(report)
            print(report)
            # maybe better exit if the ID was not found?
            # but it could happen on the start of a session with PersistMessages=Y.
            return

        self.open_orders[ClOrdID].status = ordStatus

        if ordStatus == '0':  # new
            self.open_orders[ClOrdID].openTime = transactTime

        elif ordStatus == '1' and leavesQty > 0:  # partially filled
            if leavesQty == 0:
                if ClOrdID in self.open_orders.keys():
                    del self.open_orders[ClOrdID]
            else:
                self.open_orders[ClOrdID].leaves_quantity = leavesQty
            # orderQty is the ordered quantity, cumQty the one filled.
            self.add_position(symbol, side, cumQty)
            # for consistency check. canceled quantity is orderQty-cumQty.
            self.add_canceled_quantity(symbol, side, orderQty - cumQty)

        elif ordStatus == '2':  # filled
            if ClOrdID in self.open_orders.keys():
                del self.open_orders[ClOrdID]
            self.add_position(symbol, side, cumQty)

        # if there was a partial fill before, a following canceled order can have a non-zero cumQty.
        elif ordStatus == '3':
            if ClOrdID in self.open_orders.keys():
                del self.open_orders[ClOrdID]

        report = execution_report(ClOrdID, symbol, side, price, ordType,
                                  ordStatus, orderQty, minQty, cumQty,
                                  leavesQty)
        self.execution_history.append(report)

        if self.verbose:
            print(report)

        log(
            self.execution_logger, '{},{},{},{},{},{},{},{},{},{},{}'.format(
                transactTime, ClOrdID, symbol, side, price, ordType, ordStatus,
                orderQty, minQty, cumQty, leavesQty))

        if self.read_positions_from_file:
            self.save_positions_to_file()

        self.lock.acquire()
        self.tick_processor.on_execution_report(report, self)
        self.lock.release()
    def onMessage(self, message, sessionID):
        print("OnMessage %s" % message)
        msgType = fix.MsgType()
        message.getHeader().getField(msgType)
        if (msgType.getValue() == "X"):
            print("MarketDataIncrementalRefresh %s" % message)
            noMDEntries = fix.NoMDEntries()
            message.getField(noMDEntries)
            if (noMDEntries.getValue() != 1):
                print("NoMDEntries in MarketDataIncrementalRefresh is not 1!")
                return
            group = fix44.MarketDataIncrementalRefresh.NoMDEntries()
            message.getGroup(1, group)

            entryID = fix.MDEntryID()
            group.getField(entryID)
            action = fix.MDUpdateAction()
            group.getField(action)
            actionvalue = action.getValue()  # 0=New, 1=Update, 2=Delete)
            if (actionvalue == '2'):  # delete
                if entryID.getValue() in securities:
                    del securities[entryID.getValue()]
                return
            security = SECURITY()
            security.MDEntryID = entryID.getValue()
            security.MDUpdateAction = action.getValue()
            symbol = fix.Symbol()
            if (group.isSetField(symbol)):
                group.getField(symbol)
                security.Symbol = symbol.getValue()
            entryType = fix.MDEntryType()
            if (group.isSetField(entryType)):
                group.getField(entryType)
                security.MDEntryType = entryType.getValue()
            price = fix.MDEntryPx()
            if (group.isSetField(price)):
                group.getField(price)
                security.MDEntryPx = price.getValue()
            size = fix.MDEntrySize()
            if (group.isSetField(size)):
                group.getField(size)
                security.MDEntrySize = size.getValue()
            qty = fix.MinQty()
            if (group.isSetField(qty)):
                group.getField(qty)
                security.MinQty = qty.getValue()
            inc = MinInc()
            if (message.isSetField(inc)):
                message.getField(inc)
                security.MinInc = inc.getValue()
            br = MinBr()
            if (message.isSetField(br)):
                message.getField(br)
                security.MinBR = br.getValue()
            ytm = YTM()
            if (message.isSetField(ytm)):
                message.getField(ytm)
                security.YTM = ytm.getValue()
            ytw = YTW()
            if (message.isSetField(ytw)):
                message.getField(ytw)
                security.YTW = ytw.getValue()
            print(security)
            securities[entryID.getValue()] = security
    def onMessage(self, message, sessionID):
        # print("OnMessage %s" % message)
        msgType = fix.MsgType()
        message.getHeader().getField(msgType)
        if msgType.getValue() == "X":
            # print("MarketDataIncrementalRefresh %s" % message)
            noMDEntries = fix.NoMDEntries()
            message.getField(noMDEntries)
            if (noMDEntries.getValue() != 1):
                # print("NoMDEntries in MarketDataIncrementalRefresh is not 1!")
                return
            group = fix44.MarketDataIncrementalRefresh.NoMDEntries()
            message.getGroup(1, group)

            entryID = fix.MDEntryID()
            group.getField(entryID)
            action = fix.MDUpdateAction()
            group.getField(action)
            security = LAST_TRADE()
            security.MDEntryID = entryID.getValue()
            security.MDUpdateAction = action.getValue()
            symbol = fix.Symbol()
            if (group.isSetField(symbol)):
                group.getField(symbol)
                security.Symbol = symbol.getValue()
            entryType = fix.MDEntryType()
            if (group.isSetField(entryType)):
                group.getField(entryType)
                security.MDEntryType = entryType.getValue()
            price = fix.MDEntryPx()
            if (group.isSetField(price)):
                group.getField(price)
                security.MDEntryPx = price.getValue()
            size = fix.MDEntrySize()
            if (group.isSetField(size)):
                group.getField(size)
                security.MDEntrySize = size.getValue()
            qty = fix.MinQty()
            if (group.isSetField(qty)):
                group.getField(qty)
                security.MinQty = qty.getValue()

            fire(self.callback, "OnTradeUpdated", **{"trade": security})

        if msgType.getValue() == 'W':
            book = BOOK()
            Symbol = fix.Symbol()
            message.getField(Symbol)
            book.symbol = Symbol.getValue()

            noMDEntries = fix.NoMDEntries()
            message.getField(noMDEntries)

            group = fix44.MarketDataSnapshotFullRefresh.NoMDEntries()
            MDEntryType = fix.MDEntryType()
            MDEntryPx = fix.MDEntryPx()
            MDEntrySize = fix.MDEntrySize()

            for i in range(1, noMDEntries.getValue()):
                message.getGroup(i, group)
                group.getField(MDEntryType)
                group.getField(MDEntryPx)
                group.getField(MDEntrySize)
                if MDEntryType.getValue() == '0':
                    book.bid.append(MDEntryPx.getValue())
                    book.bid_size.append(MDEntrySize.getValue())
                if MDEntryType.getValue() == '1':
                    book.ask.append(MDEntryPx.getValue())
                    book.ask_size.append(MDEntrySize.getValue())

            fire(self.callback, "OnBookUpdated", **{"book": book})