docs: def run(self):
          """Starts the blotter
  
          Connects to the TWS/GW, processes and logs market data,
          and broadcast it over TCP via ZeroMQ (which algo subscribe to)
          """
  
          self._check_unique_blotter()
  
          # connect to mysql
          self.mysql_connect()
  
          self.context = zmq.Context(zmq.REP)
          self.socket = self.context.socket(zmq.PUB)
          self.socket.bind("tcp://*:" + str(self.args['zmqport']))
  
          db_modified = 0
          contracts = []
          prev_contracts = []
          first_run = True
  
          self.log_blotter.info("Connecting to Interactive Brokers...")
          self.ibConn = ezIBpy()
          self.ibConn.ibCallback = self.ibCallback
  
          while not self.ibConn.connected:
              self.ibConn.connect(clientId=int(self.args['ibclient']),
                                  port=int(self.args['ibport']), host=str(self.args['ibserver']))
              time.sleep(1)
              if not self.ibConn.connected:
                  print('*', end="", flush=True)
          self.log_blotter.info("Connection established...")
  
          try:
              while True:
  
                  if not os.path.exists(self.args['symbols']):
                      pd.DataFrame(columns=['symbol', 'sec_type', 'exchange',
                                            'currency', 'expiry', 'strike', 'opt_type']
                                   ).to_csv(self.args['symbols'], header=True, index=False)
                      tools.chmod(self.args['symbols'])
                  else:
                      time.sleep(0.1)
  
                      # read db properties
                      db_data = os.stat(self.args['symbols'])
                      db_size = db_data.st_size
                      db_last_modified = db_data.st_mtime
  
                      # empty file
                      if db_size == 0:
                          if prev_contracts:
                              self.log_blotter.info('Cancel market data...')
                              self.ibConn.cancelMarketData()
                              time.sleep(0.1)
                              prev_contracts = []
                          continue
  
                      # modified?
                      if not first_run and db_last_modified == db_modified:
                          continue
  
                      # continue...
                      db_modified = db_last_modified
  
                      # read contructs db
                      df = pd.read_csv(self.args['symbols'], header=0)
                      if df.empty:
                          continue
  
                      # removed expired
                      df = df[(
                              (df['expiry'] < 1000000) & (
                                  df['expiry'] >= int(datetime.now().strftime('%Y%m')))) | (
                              (df['expiry'] >= 1000000) & (
                                  df['expiry'] >= int(datetime.now().strftime('%Y%m%d')))) |
                              np_isnan(df['expiry'])
                              ]
  
                      # fix expiry formatting (no floats)
                      df['expiry'] = df['expiry'].fillna(
                          0).astype(int).astype(str)
                      df.loc[df['expiry'] == "0", 'expiry'] = ""
                      df = df[df['sec_type'] != 'BAG']
  
                      df.fillna("", inplace=True)
                      df.to_csv(self.args['symbols'], header=True, index=False)
                      tools.chmod(self.args['symbols'])
  
                      # ignore commentee
                      df = df[~df['symbol'].str.contains("#")]
                      contracts = [tuple(x) for x in df.values]
  
                      if first_run:
                          first_run = False
  
                      else:
                          if contracts != prev_contracts:
                              # cancel market data for removed contracts
                              for contract in prev_contracts:
                                  if contract not in contracts:
                                      self.ibConn.cancelMarketData(
                                          self.ibConn.createContract(contract))
                                      if self.args['orderbook']:
                                          self.ibConn.cancelMarketDepth(
                                              self.ibConn.createContract(contract))
                                      time.sleep(0.1)
                                      contract_string = self.ibConn.contractString(
                                          contract).split('_')[0]
                                      self.log_blotter.info(
                                          'Contract Removed [%s]', contract_string)
  
                      # request market data
                      for contract in contracts:
                          if contract not in prev_contracts:
                              self.ibConn.requestMarketData(
                                  self.ibConn.createContract(contract))
                              if self.args['orderbook']:
                                  self.ibConn.requestMarketDepth(
                                      self.ibConn.createContract(contract))
                              time.sleep(0.1)
                              contract_string = self.ibConn.contractString(
                                  contract).split('_')[0]
                              self.log_blotter.info(
                                  'Contract Added [%s]', contract_string)
  
                      # update latest contracts
                      prev_contracts = contracts
  
                  time.sleep(2)
  
          except (KeyboardInterrupt, SystemExit):
              self.quitting = True  # don't display connection errors on ctrl+c
              print(
                  "\n\n>>> Interrupted with Ctrl-c...\n(waiting for running tasks to be completed)\n")
              # asynctools.multitasking.killall() # stop now
              asynctools.multitasking.wait_for_tasks()  # wait for threads to complete
              sys.exit(1)