Starts the blotter

        Connects to the TWS/GW, processes and logs market data,
        and broadcast it over TCP via ZeroMQ (which algo subscribe to)

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)