def fromAdmin(self, message, sessionID):

        if self.verbose:
            print(f'[fromAdmin] {sessionID} | {read_FIX_message(message)}')

        log(self.logger,
            f'[fromAdmin] {sessionID} | {read_FIX_message(message)}')

        msgType = fix.MsgType()
        message.getHeader().getField(msgType)

        base_str = self._server_str + ' ' + sessionID.toString() + ' | '

        if msgType.getValue() == fix.MsgType_Heartbeat:

            if self.verbose:
                print(base_str + 'Heartbeat, right back at ya!')

        elif msgType.getValue() == fix.MsgType_Logon:

            print(base_str + 'Hello there, good to have you back!')

        elif msgType.getValue() == fix.MsgType_Logout:

            print(base_str + 'Logout. See you later!')

        elif self.verbose:
            print(f'[fromAdmin] {sessionID} | {read_FIX_message(message)}')

            if msgType.getValue() != fix.MsgType_SequenceReset:
                print('unknown message type: ', msgType)
    def fromApp(self, message, sessionID):

        if self.verbose:
            print(f'[fromApp] {sessionID} | {read_FIX_message(message)}')

        log(self.logger,
            f'[fromApp] {sessionID} | {read_FIX_message(message)}')

        # Get incoming message Type
        msgType = fix.MsgType()
        message.getHeader().getField(msgType)
        msgType = msgType.getValue()

        # Get timestamp (tag 52)
        sending_time = extract_message_field_value(fix.SendingTime(), message,
                                                   'datetime')
        # print('sending_time:', sending_time)

        ########## Quote messages ##########

        if msgType == fix.MsgType_MassQuote:

            self.parse_MassQuote(message, sending_time)

        elif msgType == fix.MsgType_MarketDataSnapshotFullRefresh:

            self.parse_MarketDataSnapshotFullRefresh(message, sending_time)

        # 3) Process MarketDataSnapshot_IncrementalRefresh message
        elif msgType == fix.MsgType_MarketDataIncrementalRefresh:

            print(self._server_str + ' {MD} INCREMENTAL REFRESH!')

        ########## Trade messages ##########

        elif msgType == fix.MsgType_ExecutionReport:

            self.parse_ExecutionReport(message, sending_time)

        elif msgType == fix.MsgType_OrderCancelReject:

            # An OrderCancelReject will be sent as an answer to an  OrderCancelRequest, which cannot be executed.
            # Not much to do here as our order dict would stay the same.
            # If it was canceled successfully, we should get an execution report.

            ClOrdID = extract_message_field_value(fix.ClOrdID(), message,
                                                  'int')

            print(
                f'[fromApp] Order Cancel Request Rejected for order: {ClOrdID}'
            )

        elif msgType == fix.MsgType_MarketDataRequestReject:

            text = extract_message_field_value(fix.Text(), message, 'str')
            print(f'[fromApp] Market Data Request Reject with message: {text}')

        elif self.verbose:
            print(f'[fromApp] {sessionID} | {read_FIX_message(message)}')
            print('unknown message type: ', msgType)
    def toAdmin(self, message, sessionID):

        if self.verbose:
            print(f'[toAdmin] {sessionID} | {read_FIX_message(message)}')

        log(self.logger,
            f'[toAdmin] {sessionID} | {read_FIX_message(message)}')

        msgType = fix.MsgType()
        message.getHeader().getField(msgType)

        base_str = self._client_str + ' ' + sessionID.toString() + ' | '

        if msgType.getValue() == fix.MsgType_Logon:

            print(self._client_str + 'Sending LOGON Request')

            # set login credentials
            username = self.settings.get(sessionID).getString('Username')
            password = self.settings.get(sessionID).getString('Password')
            message.setField(fix.Username(username))
            message.setField(fix.Password(password))

        elif msgType.getValue() == fix.MsgType_Logout:

            print(self._client_str + 'Sending LOGOUT Request')

        elif (msgType.getValue() == fix.MsgType_Heartbeat):

            if self.verbose:
                print(self._client_str + 'Heartbeat!')

        else:
            print(f'[toAdmin] {sessionID} | {read_FIX_message(message)}')
    def _update_asset(self, date_time, _symbol, depth, bid, ask, bid_size, ask_size):
        
        """
        Check all fields and update data accordingly. Not all will
        be present in every message.
        """
        
        if depth is None or _symbol != self.symbol:
            return
        
        new_tob_bid, new_tob_ask = False, False

        # replace with current value if there is no update
        if bid is not None:
            if depth < self._lowest_bid_depth or self._lowest_bid_depth == -1:
                self._lowest_bid_depth = depth
            self.BID[depth] = bid
            if depth == self._lowest_bid_depth:
                self.BID_TOB = bid
                new_tob_bid = True
        if ask is not None:
            if depth < self._lowest_ask_depth or self._lowest_ask_depth == -1:
                self._lowest_ask_depth = depth
            self.ASK[depth] = ask
            if depth == self._lowest_bid_depth:
                self.ASK_TOB = ask
                new_tob_ask = True
        if bid_size is not None:
            self.BID_SIZE[depth] = bid_size
        if ask_size is not None:
            self.ASK_SIZE[depth] = ask_size
        
        # only save complete ticks
        if self.BID[depth] is None or self.ASK[depth] is None or self.BID_SIZE[depth] is None or self.ASK_SIZE[depth] is None:
            return
        

        if self.store_all_ticks:

            try:
                self.HISTORY.append({'date_time': date_time, 'depth': depth, 'bid': 
                                     self.BID[depth], 'ask': self.ASK[depth], 
                                    'bid_size': self.BID_SIZE[depth], 
                                    'ask_size': self.ASK_SIZE[depth]})
                log(self.history_logger, '{},{},{},{},{},{}'.format(date_time, depth, 
                                                                    self.BID[depth], self.ASK[depth], 
                                                                    self.BID_SIZE[depth], 
                                                                    self.ASK_SIZE[depth]))
                
                if new_tob_bid or new_tob_ask:
                    self.HISTORY_TOB.append({'date_time': date_time, 
                                             'bid': self.BID_TOB, 
                                             'ask': self.ASK_TOB})
                    log(self.history_tob_logger, '{},{},{}'.format(date_time, self.BID_TOB, self.ASK_TOB))

            except KeyError:
                pass
    def onCreate(self, sessionID):

        if self.settings.get(sessionID).getString(
                'SessionQualifier') == 'Quote':
            print('SessionQualifier=Quote, sessionID=' + sessionID.toString())
            self.sender.set_sessionID_Quote(sessionID)

        elif self.settings.get(sessionID).getString(
                'SessionQualifier') == 'Trade':
            print('SessionQualifier=Trade, sessionID=' + sessionID.toString())
            self.sender.set_sessionID_Trade(sessionID)
            self.sender.set_account(
                self.settings.get(sessionID).getString('Account'))

        log(self.logger,
            f'Session created with sessionID = {sessionID.toString()}.')
    def __init__(self, _symbol, store_all_ticks=True, save_history_to_files=True):
        
        self.symbol = _symbol
        self.save_history_to_files = save_history_to_files
        self.store_all_ticks = store_all_ticks

        # current bid/ask values depending on depth. top of book is self.BID[0]. 
        self.BID = {}
        self.ASK = {}
        self.BID_SIZE = {}
        self.ASK_SIZE = {}
        self.BID_TOB = 0
        self.ASK_TOB = 0
        self._lowest_bid_depth = -1
        self._lowest_ask_depth = -1
        
        self.HISTORY_DIR = 'history'
        Path(self.HISTORY_DIR).mkdir(parents=True, exist_ok=True)

        self.HISTORY_FILE = f"{self.symbol.replace('/', '')}.log"
        self.HISTORY_FILE_TOB = f"{self.symbol.replace('/', '')}_TOB.log"

        self.history_logger = setup_logger('history_logger', 
                                           join(self.HISTORY_DIR, self.HISTORY_FILE), 
                                           '%(message)s', 
                                           level=logging.INFO)
        self.history_tob_logger = setup_logger('history_tob_logger', 
                                               join(self.HISTORY_DIR, self.HISTORY_FILE_TOB), 
                                               '%(message)s', 
                                               level=logging.INFO)

        log(self.history_logger, 'date_time,depth,bid,ask,bid_size,ask_size')
        log(self.history_tob_logger, 'date_time,bid,ask')

        self.HISTORY = []
        self.HISTORY_TOB = []
    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 __init__(
            self,
            settings,
            tick_processor,
            read_positions_from_file=False,  # to load positions after restart
            store_all_ticks=True,
            save_history_to_files=True,
            verbose=True,
            message_log_file='messages.log',
            execution_history_file='execution_history.log',
            client_str='[CLIENT (FIX API v4.4)] ',
            server_str='[SERVER (FIX API v4.4)] '):

        super().__init__()
        self.store_all_ticks = store_all_ticks
        self.save_history_to_files = save_history_to_files
        self.verbose = verbose
        self._position_file = 'positions.json'
        self._order_file = 'orders.json'
        self.execution_history_file = execution_history_file
        self.connected = False
        self._ID_Incrementor = 0
        self.ClOrdID_Incrementor = 0
        self.settings = settings
        self.tick_processor = tick_processor
        self.lock = Lock()
        self.sender = sender(
            self
        )  # passing self here so that we can call app functions from there.

        self.logger = None
        if len(message_log_file) > 0:
            self.logger = setup_logger('message_logger',
                                       message_log_file,
                                       '%(asctime)s %(levelname)s %(message)s',
                                       level=logging.INFO)

        self.execution_logger = None
        if len(execution_history_file) > 0:
            self.execution_logger = setup_logger('execution_logger',
                                                 execution_history_file,
                                                 '%(message)s',
                                                 level=logging.INFO)
            log(
                self.execution_logger,
                'transactTime,ClOrdID,Symbol,Side,Price,OrdType,OrdStatus,OrderQty,MinQty,CumQty,LeavesQty'
            )

        self._client_str = client_str
        self._server_str = server_str

        # Unique identifier for Market Data Request <V>
        self._id_to_symbol = {}  # format: '0': 'EURUSD'

        # Dictionary to hold Asset Histories
        self.history_dict = {}  # format: 'EURUSD': History

        # Dictionary to hold open orders
        self.open_orders = {}  # format: ClOrdID: Order

        self.open_net_positions = {}  # format: 'EURUSD': 0.0
        self.canceled_net_quantity = {}  # format: 'EURUSD': 0.0

        self.execution_history = []

        self.read_positions_from_file = read_positions_from_file

        if self.read_positions_from_file:
            self.load_positions_from_file()
    def toApp(self, message, sessionID):

        if self.verbose:
            print(f'[toApp] {sessionID} | {read_FIX_message(message)}')

        log(self.logger, f'[toApp] {sessionID} | {read_FIX_message(message)}')
    def onMessage(self, message, sessionID):

        if self.verbose:
            print('[onMessage] {}'.format(read_FIX_message(message)))

        log(self.logger, f'Message = {read_FIX_message(message)}.')
    def onLogout(self, sessionID):

        self.connected = False
        log(self.logger, 'Logout.')
        print(self._client_str + ' ' + sessionID.toString() +
              ' | Logout Successful')
    def onLogon(self, sessionID):

        self.connected = True
        log(self.logger, 'Logon.')
        print(self._client_str + ' ' + sessionID.toString() +
              ' | Login Successful')