Example #1
0
    def update_notify(self):
        if not self.config['workers']:
            log.critical("No workers to launch, exiting")
            raise errors.NoWorkersAvailable()

        if self.notify:
            # Update the notification instance
            self.notify.reset_subscriptions(list(self.accounts),
                                            list(self.markets))
        else:
            # Initialize the notification instance
            self.notify = Notify(markets=list(self.markets),
                                 accounts=list(self.accounts),
                                 on_market=self.on_market,
                                 on_account=self.on_account,
                                 on_block=self.on_block,
                                 bitshares_instance=self.bitshares)
Example #2
0
    def __init__(
        self,
        config,
        bitshares_instance=None,
    ):
        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()

        self.config = config

        # Load all accounts and markets in use to subscribe to them
        accounts = set()
        markets = set()
        for botname, bot in config["bots"].items():
            if "account" not in bot:
                raise ValueError("Bot %s has no account" % botname)
            if "market" not in bot:
                raise ValueError("Bot %s has no market" % botname)

            accounts.add(bot["account"])
            markets.add(bot["market"])

        # Create notification instance
        # Technically, this will multiplex markets and accounts and
        # we need to demultiplex the events after we have received them
        self.notify = Notify(markets=list(markets),
                             accounts=list(accounts),
                             on_market=self.on_market,
                             on_account=self.on_account,
                             on_block=self.on_block,
                             bitshares_instance=self.bitshares)

        # Initialize bots:
        for botname, bot in config["bots"].items():
            klass = getattr(importlib.import_module(bot["module"]), bot["bot"])
            self.bots[botname] = klass(config=config,
                                       name=botname,
                                       bitshares_instance=self.bitshares)
Example #3
0
    def __init__(
        self,
        config,
        bitshares_instance=None,
    ):
        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()

        self.config = config

        # Load all accounts and markets in use to subscribe to them
        accounts = set()
        markets = set()
        for botname, bot in config["bots"].items():
            if "account" not in bot:
                raise ValueError("Bot %s has no account" % botname)
            if "market" not in bot:
                raise ValueError("Bot %s has no market" % botname)

            accounts.add(bot["account"])
            markets.add(bot["market"])

        # Create notification instance
        # Technically, this will multiplex markets and accounts and
        # we need to demultiplex the events after we have received them
        self.notify = Notify(
            markets=list(markets),
            accounts=list(accounts),
            on_market=self.on_market,
            on_account=self.on_account,
            on_block=self.on_block,
            bitshares_instance=self.bitshares
        )

        # Initialize bots:
        for botname, bot in config["bots"].items():
            klass = getattr(
                importlib.import_module(bot["module"]),
                bot["bot"]
            )
            self.bots[botname] = klass(
                config=config,
                name=botname,
                bitshares_instance=self.bitshares
            )
Example #4
0
class WorkerInfrastructure(threading.Thread):
    def __init__(self, config, bitshares_instance=None, view=None):
        super().__init__()
        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()
        self.config = copy.deepcopy(config)
        self.view = view
        self.jobs = []
        self.notify = None
        self.config_lock = threading.RLock()
        self.workers = {}

        self.accounts = set()
        self.markets = set()

        # Set the module search path
        user_worker_path = os.path.expanduser("~/bots")
        if os.path.exists(user_worker_path):
            sys.path.append(user_worker_path)

    def init_workers(self, config):
        """ Initialize the workers
        """
        # set up reporting
        self.reporters = []
        for reporter_params in self.config.get("reports", []):
            reporter_class = getattr(
                importlib.import_module(reporter_params['module']),
                reporter_params.get('class', 'Reporter'))
            reporter_params = reporter_params.copy()
            del reporter_params['module']
            if 'class' in reporter_params:
                del reporter_params['class']
            reporter_params['worker_infrastructure'] = self
            reporter_instance = reporter_class(**reporter_params)
            self.reporters.append(reporter_instance)

        # set up workers
        self.config_lock.acquire()
        for worker_name, worker in config["workers"].items():
            if "account" not in worker:
                log_workers.critical("Worker has no account",
                                     extra={
                                         'worker_name': worker_name,
                                         'account': 'unknown',
                                         'market': 'unknown',
                                         'is_disabled': (lambda: True)
                                     })
                continue
            if "market" not in worker:
                log_workers.critical("Worker has no market",
                                     extra={
                                         'worker_name': worker_name,
                                         'account': worker['account'],
                                         'market': 'unknown',
                                         'is_disabled': (lambda: True)
                                     })
                continue
            try:
                strategy_class = getattr(
                    importlib.import_module(worker["module"]), 'Strategy')
                self.workers[worker_name] = strategy_class(
                    config=config,
                    name=worker_name,
                    bitshares_instance=self.bitshares,
                    view=self.view)
                self.markets.add(worker['market'])
                self.accounts.add(worker['account'])
            except BaseException:
                log_workers.exception("Worker initialisation",
                                      extra={
                                          'worker_name': worker_name,
                                          'account': worker['account'],
                                          'market': 'unknown',
                                          'is_disabled': (lambda: True)
                                      })
        self.config_lock.release()

    def update_notify(self):
        if not self.config['workers']:
            log.critical("No workers configured to launch, exiting")
            raise errors.NoWorkersAvailable()
        if not self.workers:
            log.critical("No workers actually running")
            raise errors.NoWorkersAvailable()
        if self.notify:
            # Update the notification instance
            self.notify.reset_subscriptions(list(self.accounts),
                                            list(self.markets))
        else:
            # Initialize the notification instance
            self.notify = Notify(markets=list(self.markets),
                                 accounts=list(self.accounts),
                                 on_market=self.on_market,
                                 on_account=self.on_account,
                                 on_block=self.on_block,
                                 bitshares_instance=self.bitshares)

    def shutdown(self):
        for i in self.reporters:
            i.shutdown()

    # Events
    def on_block(self, data):
        if self.jobs:
            try:
                for job in self.jobs:
                    job()
            finally:
                self.jobs = []

        self.config_lock.acquire()
        for reporter in self.reporters:
            reporter.ontick()
        for worker_name, worker in self.config["workers"].items():
            if worker_name not in self.workers or self.workers[
                    worker_name].disabled:
                continue
            try:
                self.workers[worker_name].ontick(data)
            except Exception as e:
                self.workers[worker_name].log.exception("in ontick()")
                try:
                    self.workers[worker_name].error_ontick(e)
                except Exception:
                    self.workers[worker_name].log.exception(
                        "in error_ontick()")
        self.config_lock.release()

    def on_market(self, data):
        if data.get("deleted", False):  # No info available on deleted orders
            return

        self.config_lock.acquire()
        for worker_name, worker in self.config["workers"].items():
            if self.workers[worker_name].disabled:
                self.workers[worker_name].log.debug(
                    'Worker "{}" is disabled'.format(worker_name))
                continue
            if worker["market"] == data.market:
                try:
                    self.workers[worker_name].onMarketUpdate(data)
                except Exception as e:
                    self.workers[worker_name].log.exception(
                        "in onMarketUpdate()")
                    try:
                        self.workers[worker_name].error_onMarketUpdate(e)
                    except Exception:
                        self.workers[worker_name].log.exception(
                            "in error_onMarketUpdate()")
        self.config_lock.release()

    def on_account(self, account_update):
        self.config_lock.acquire()
        account = account_update.account
        for worker_name, worker in self.config["workers"].items():
            if self.workers[worker_name].disabled:
                self.workers[worker_name].log.info(
                    'Worker "{}" is disabled'.format(worker_name))
                continue
            if worker["account"] == account["name"]:
                try:
                    self.workers[worker_name].onAccount(account_update)
                except Exception as e:
                    self.workers[worker_name].log.exception(
                        "in onAccountUpdate()")
                    try:
                        self.workers[worker_name].error_onAccount(e)
                    except Exception:
                        self.workers[worker_name].log.exception(
                            "in error_onAccountUpdate()")
        self.config_lock.release()

    def add_worker(self, worker_name, config):
        with self.config_lock:
            self.config['workers'][worker_name] = config['workers'][
                worker_name]
            self.init_workers(config)
        self.update_notify()

    def run(self):
        self.init_workers(self.config)
        self.update_notify()
        self.notify.listen()

    def stop(self, worker_name=None, pause=False):
        """ Used to stop the worker(s)
            :param str worker_name: name of the worker to stop
            :param bool pause: optional argument which tells worker if it was
                stopped or just paused
        """
        if worker_name and len(self.workers) > 1:
            # Kill only the specified worker
            self.remove_market(worker_name)
            with self.config_lock:
                account = self.config['workers'][worker_name]['account']
                self.config['workers'].pop(worker_name)

            self.accounts.remove(account)
            if pause:
                self.workers[worker_name].pause()
            self.workers.pop(worker_name, None)
            self.update_notify()
        else:
            # Kill all of the workers
            if pause:
                for worker in self.workers:
                    self.workers[worker].pause()
            if self.notify:
                self.notify.websocket.close()
            self.shutdown()

    def remove_worker(self, worker_name=None):
        if worker_name:
            self.workers[worker_name].purge()
        else:
            for worker in self.workers:
                self.workers[worker].purge()

    def remove_market(self, worker_name):
        """ Remove the market only if the worker is the only one using it
        """
        with self.config_lock:
            market = self.config['workers'][worker_name]['market']
            for name, worker in self.config['workers'].items():
                if market == worker['market']:
                    break  # Found the same market, do nothing
            else:
                # No markets found, safe to remove
                self.markets.remove(market)

    @staticmethod
    def remove_offline_worker(config, worker_name):
        # Initialize the base strategy to get control over the data
        bitshares_instance = BitShares(config['node'])
        strategy = BaseStrategy(worker_name,
                                config,
                                bitshares_instance=bitshares_instance)
        strategy.purge()

    @staticmethod
    def remove_offline_worker_data(worker_name):
        BaseStrategy.purge_worker_data(worker_name)

    def do_next_tick(self, job):
        """ Add a callable to be executed on the next tick """
        self.jobs.append(job)
            op['trxid'] = trxid

            if trxid == latest_order:
                new_orders.append(op)
                break

            if first_order == None:
                first_order = trxid

        if latest_order != None and len(new_orders):
            # Reversed sort order to get them in timeline order
            for op in new_orders[::-1]:
                # modify message
                send_msg = dataParser(op)
                send_tg_message('*' + username + '*: ' +
                                send_msg.replace('sell', '_sold_'))

        if first_order:
            config.set(username, 'last_order_id', first_order)

    with open(conffile, 'w+') as configfile:
        config.write(configfile)


bitshares = BitShares(node=conf.NODES)
set_shared_bitshares_instance(bitshares)

notify = Notify(on_block=on_block, bitshares_instance=bitshares)
print("Listening..")
notify.listen()
Example #6
0
class BotInfrastructure():

    bots = dict()

    def __init__(
        self,
        config,
        bitshares_instance=None,
    ):
        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()

        self.config = config

        # Load all accounts and markets in use to subscribe to them
        accounts = set()
        markets = set()
        for botname, bot in config["bots"].items():
            if "account" not in bot:
                raise ValueError("Bot %s has no account" % botname)
            if "market" not in bot:
                raise ValueError("Bot %s has no market" % botname)

            accounts.add(bot["account"])
            markets.add(bot["market"])

        # Create notification instance
        # Technically, this will multiplex markets and accounts and
        # we need to demultiplex the events after we have received them
        self.notify = Notify(
            markets=list(markets),
            accounts=list(accounts),
            on_market=self.on_market,
            on_account=self.on_account,
            on_block=self.on_block,
            bitshares_instance=self.bitshares
        )

        # Initialize bots:
        for botname, bot in config["bots"].items():
            klass = getattr(
                importlib.import_module(bot["module"]),
                bot["bot"]
            )
            self.bots[botname] = klass(
                config=config,
                name=botname,
                bitshares_instance=self.bitshares
            )

    # Events
    def on_block(self, data):
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                continue
            try:
                self.bots[botname].ontick(data)
            except Exception as e:
                self.bots[botname].error_ontick(e)
                log.error(
                    "Error while processing {botname}.tick(): {exception}\n{stack}".format(
                        botname=botname,
                        exception=str(e),
                        stack=traceback.format_exc()
                    ))

    def on_market(self, data):
        if data.get("deleted", False):  # no info available on deleted orders
            return
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                log.info("The bot %s has been disabled" % botname)
                continue
            if bot["market"] == data.market:
                try:
                    self.bots[botname].onMarketUpdate(data)
                except Exception as e:
                    self.bots[botname].error_onMarketUpdate(e)
                    log.error(
                        "Error while processing {botname}.onMarketUpdate(): {exception}\n{stack}".format(
                            botname=botname,
                            exception=str(e),
                            stack=traceback.format_exc()
                        ))

    def on_account(self, accountupdate):
        account = accountupdate.account
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                log.info("The bot %s has been disabled" % botname)
                continue
            if bot["account"] == account["name"]:
                try:
                    self.bots[botname].onAccount(accountupdate)
                except Exception as e:
                    self.bots[botname].error_onAccount(e)
                    log.error(
                        "Error while processing {botname}.onAccount(): {exception}\n{stack}".format(
                            botname=botname,
                            exception=str(e),
                            stack=traceback.format_exc()
                        ))

    def run(self):
        self.notify.listen()
Example #7
0
class BotInfrastructure():

    bots = dict()

    def __init__(
        self,
        config,
        bitshares_instance=None,
    ):
        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()

        self.config = config

        # Load all accounts and markets in use to subscribe to them
        accounts = set()
        markets = set()
        for botname, bot in config["bots"].items():
            if "account" not in bot:
                raise ValueError("Bot %s has no account" % botname)
            if "market" not in bot:
                raise ValueError("Bot %s has no market" % botname)

            accounts.add(bot["account"])
            markets.add(bot["market"])

        # Create notification instance
        # Technically, this will multiplex markets and accounts and
        # we need to demultiplex the events after we have received them
        self.notify = Notify(markets=list(markets),
                             accounts=list(accounts),
                             on_market=self.on_market,
                             on_account=self.on_account,
                             on_block=self.on_block,
                             bitshares_instance=self.bitshares)

        # Initialize bots:
        for botname, bot in config["bots"].items():
            klass = getattr(importlib.import_module(bot["module"]), bot["bot"])
            self.bots[botname] = klass(config=config,
                                       name=botname,
                                       bitshares_instance=self.bitshares)

    # Events
    def on_block(self, data):
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                continue
            try:
                self.bots[botname].ontick(data)
            except Exception as e:
                self.bots[botname].error_ontick(e)
                log.error(
                    "Error while processing {botname}.tick(): {exception}\n{stack}"
                    .format(botname=botname,
                            exception=str(e),
                            stack=traceback.format_exc()))

    def on_market(self, data):
        if data.get("deleted", False):  # no info available on deleted orders
            return
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                log.info("The bot %s has been disabled" % botname)
                continue
            if bot["market"] == data.market:
                try:
                    self.bots[botname].onMarketUpdate(data)
                except Exception as e:
                    self.bots[botname].error_onMarketUpdate(e)
                    log.error(
                        "Error while processing {botname}.onMarketUpdate(): {exception}\n{stack}"
                        .format(botname=botname,
                                exception=str(e),
                                stack=traceback.format_exc()))

    def on_account(self, accountupdate):
        account = accountupdate.account
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                log.info("The bot %s has been disabled" % botname)
                continue
            if bot["account"] == account["name"]:
                try:
                    self.bots[botname].onAccount(accountupdate)
                except Exception as e:
                    self.bots[botname].error_onAccount(e)
                    log.error(
                        "Error while processing {botname}.onAccount(): {exception}\n{stack}"
                        .format(botname=botname,
                                exception=str(e),
                                stack=traceback.format_exc()))

    def run(self):
        self.notify.listen()
Example #8
0
    def __init__(self, config, bitshares_instance=None, gui_data=None):
        super().__init__()
        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()

        self.config = config

        # set the module search path
        user_bot_path = os.path.expanduser("~/bots")
        if os.path.exists(user_bot_path):
            sys.path.append(user_bot_path)

        # Load all accounts and markets in use to subscribe to them
        accounts = set()
        markets = set()

        # Initialize bots:
        for botname, bot in config["bots"].items():
            if "account" not in bot:
                log_bots.critical("Bot has no account",
                                  extra={
                                      'botname': botname,
                                      'account': 'unknown',
                                      'market': 'unknown',
                                      'is_dsabled': (lambda: True)
                                  })
                continue
            if "market" not in bot:
                log_bots.critical("Bot has no market",
                                  extra={
                                      'botname': botname,
                                      'account': bot['account'],
                                      'market': 'unknown',
                                      'is_disabled': (lambda: True)
                                  })
                continue
            try:
                klass = getattr(importlib.import_module(bot["module"]),
                                'Strategy')
                self.bots[botname] = klass(config=config,
                                           name=botname,
                                           bitshares_instance=self.bitshares,
                                           gui_data=gui_data)
                markets.add(bot['market'])
                accounts.add(bot['account'])
            except:
                log_bots.exception("Bot initialisation",
                                   extra={
                                       'botname': botname,
                                       'account': bot['account'],
                                       'market': 'unknown',
                                       'is_disabled': (lambda: True)
                                   })

        if len(markets) == 0:
            log.critical("No bots to launch, exiting")
            sys.exit(70)  # 70= "Software error" in /usr/include/sysexts.h
        # Create notification instance
        # Technically, this will multiplex markets and accounts and
        # we need to demultiplex the events after we have received them
        self.notify = Notify(markets=list(markets),
                             accounts=list(accounts),
                             on_market=self.on_market,
                             on_account=self.on_account,
                             on_block=self.on_block,
                             bitshares_instance=self.bitshares)
Example #9
0
class BotInfrastructure(Process):

    bots = dict()

    def __init__(self, config, bitshares_instance=None, gui_data=None):
        super().__init__()
        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()

        self.config = config

        # set the module search path
        user_bot_path = os.path.expanduser("~/bots")
        if os.path.exists(user_bot_path):
            sys.path.append(user_bot_path)

        # Load all accounts and markets in use to subscribe to them
        accounts = set()
        markets = set()

        # Initialize bots:
        for botname, bot in config["bots"].items():
            if "account" not in bot:
                log_bots.critical("Bot has no account",
                                  extra={
                                      'botname': botname,
                                      'account': 'unknown',
                                      'market': 'unknown',
                                      'is_dsabled': (lambda: True)
                                  })
                continue
            if "market" not in bot:
                log_bots.critical("Bot has no market",
                                  extra={
                                      'botname': botname,
                                      'account': bot['account'],
                                      'market': 'unknown',
                                      'is_disabled': (lambda: True)
                                  })
                continue
            try:
                klass = getattr(importlib.import_module(bot["module"]),
                                'Strategy')
                self.bots[botname] = klass(config=config,
                                           name=botname,
                                           bitshares_instance=self.bitshares,
                                           gui_data=gui_data)
                markets.add(bot['market'])
                accounts.add(bot['account'])
            except:
                log_bots.exception("Bot initialisation",
                                   extra={
                                       'botname': botname,
                                       'account': bot['account'],
                                       'market': 'unknown',
                                       'is_disabled': (lambda: True)
                                   })

        if len(markets) == 0:
            log.critical("No bots to launch, exiting")
            sys.exit(70)  # 70= "Software error" in /usr/include/sysexts.h
        # Create notification instance
        # Technically, this will multiplex markets and accounts and
        # we need to demultiplex the events after we have received them
        self.notify = Notify(markets=list(markets),
                             accounts=list(accounts),
                             on_market=self.on_market,
                             on_account=self.on_account,
                             on_block=self.on_block,
                             bitshares_instance=self.bitshares)

    # Events
    def on_block(self, data):
        for botname, bot in self.config["bots"].items():
            if (not botname in self.bots) or self.bots[botname].disabled:
                continue
            try:
                self.bots[botname].ontick(data)
            except Exception as e:
                self.bots[botname].error_ontick(e)
                self.bots[botname].log.exception("in .tick()")

    def on_market(self, data):
        if data.get("deleted", False):  # no info available on deleted orders
            return
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                self.bots[botname].log.warning("disabled")
                continue
            if bot["market"] == data.market:
                try:
                    self.bots[botname].onMarketUpdate(data)
                except Exception as e:
                    self.bots[botname].error_onMarketUpdate(e)
                    self.bots[botname].log.exception(".onMarketUpdate()")

    def on_account(self, accountupdate):
        account = accountupdate.account
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                self.bots[botname].log.info("The bot %s has been disabled" %
                                            botname)
                continue
            if bot["account"] == account["name"]:
                try:
                    self.bots[botname].onAccount(accountupdate)
                except Exception as e:
                    self.bots[botname].error_onAccount(e)
                    self.bots[botname].log.exception(".onAccountUpdate()")

    def run(self):
        self.notify.listen()
Example #10
0
    def init_bots(self):
        """Do the actual initialisation of bots
        Potentially quite slow (tens of seconds)
        So called as part of run()
        """
        # set the module search path
        user_bot_path = os.path.expanduser("~/bots")
        if os.path.exists(user_bot_path):
            sys.path.append(user_bot_path)

        # Load all accounts and markets in use to subscribe to them
        accounts = set()
        markets = set()
        # Initialize bots:
        for botname, bot in self.config["bots"].items():
            if "account" not in bot:
                log_bots.critical("Bot has no account",
                                  extra={
                                      'botname': botname,
                                      'account': 'unknown',
                                      'market': 'unknown',
                                      'is_dsabled': (lambda: True)
                                  })
                continue
            if "market" not in bot:
                log_bots.critical("Bot has no market",
                                  extra={
                                      'botname': botname,
                                      'account': bot['account'],
                                      'market': 'unknown',
                                      'is_disabled': (lambda: True)
                                  })
                continue
            try:
                klass = getattr(importlib.import_module(bot["module"]),
                                'Strategy')
                self.bots[botname] = klass(config=self.config,
                                           name=botname,
                                           bitshares_instance=self.bitshares,
                                           view=self.view)
                markets.add(bot['market'])
                accounts.add(bot['account'])
            except BaseException:
                log_bots.exception("Bot initialisation",
                                   extra={
                                       'botname': botname,
                                       'account': bot['account'],
                                       'market': 'unknown',
                                       'is_disabled': (lambda: True)
                                   })

        if len(markets) == 0:
            log.critical("No bots to launch, exiting")
            raise errors.NoBotsAvailable()

        # Create notification instance
        # Technically, this will multiplex markets and accounts and
        # we need to demultiplex the events after we have received them
        self.notify = Notify(markets=list(markets),
                             accounts=list(accounts),
                             on_market=self.on_market,
                             on_account=self.on_account,
                             on_block=self.on_block,
                             bitshares_instance=self.bitshares)

        # set up reporting

        if "reports" in self.config:
            self.reporter = reports.Reporter(self.config['reports'], self.bots)
        else:
            self.reporter = None
Example #11
0
class BotInfrastructure(threading.Thread):

    bots = dict()

    def __init__(self, config, bitshares_instance=None, view=None):
        """Initialise variables. But no bot setup, therefore fast"""
        super().__init__()

        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()
        self.config = config
        self.view = view
        self.jobs = set()

    def init_bots(self):
        """Do the actual initialisation of bots
        Potentially quite slow (tens of seconds)
        So called as part of run()
        """
        # set the module search path
        user_bot_path = os.path.expanduser("~/bots")
        if os.path.exists(user_bot_path):
            sys.path.append(user_bot_path)

        # Load all accounts and markets in use to subscribe to them
        accounts = set()
        markets = set()
        # Initialize bots:
        for botname, bot in self.config["bots"].items():
            if "account" not in bot:
                log_bots.critical("Bot has no account",
                                  extra={
                                      'botname': botname,
                                      'account': 'unknown',
                                      'market': 'unknown',
                                      'is_dsabled': (lambda: True)
                                  })
                continue
            if "market" not in bot:
                log_bots.critical("Bot has no market",
                                  extra={
                                      'botname': botname,
                                      'account': bot['account'],
                                      'market': 'unknown',
                                      'is_disabled': (lambda: True)
                                  })
                continue
            try:
                klass = getattr(importlib.import_module(bot["module"]),
                                'Strategy')
                self.bots[botname] = klass(config=self.config,
                                           name=botname,
                                           bitshares_instance=self.bitshares,
                                           view=self.view)
                markets.add(bot['market'])
                accounts.add(bot['account'])
            except BaseException:
                log_bots.exception("Bot initialisation",
                                   extra={
                                       'botname': botname,
                                       'account': bot['account'],
                                       'market': 'unknown',
                                       'is_disabled': (lambda: True)
                                   })

        if len(markets) == 0:
            log.critical("No bots to launch, exiting")
            raise errors.NoBotsAvailable()

        # Create notification instance
        # Technically, this will multiplex markets and accounts and
        # we need to demultiplex the events after we have received them
        self.notify = Notify(markets=list(markets),
                             accounts=list(accounts),
                             on_market=self.on_market,
                             on_account=self.on_account,
                             on_block=self.on_block,
                             bitshares_instance=self.bitshares)

        # set up reporting

        if "reports" in self.config:
            self.reporter = reports.Reporter(self.config['reports'], self.bots)
        else:
            self.reporter = None

    # Events
    def on_block(self, data):
        if self.jobs:
            try:
                for i in self.jobs:
                    i()
            finally:
                self.jobs = set()
        if self.reporter is not None:
            self.reporter.ontick()
        for botname, bot in self.config["bots"].items():
            if botname not in self.bots or self.bots[botname].disabled:
                continue
            try:
                self.bots[botname].ontick(data)
            except Exception as e:
                self.bots[botname].error_ontick(e)
                self.bots[botname].log.exception("in .tick()")

    def on_market(self, data):
        if data.get("deleted", False):  # no info available on deleted orders
            return
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                continue
            if bot["market"] == data.market:
                try:
                    self.bots[botname].onMarketUpdate(data)
                except Exception as e:
                    self.bots[botname].error_onMarketUpdate(e)
                    self.bots[botname].log.exception(".onMarketUpdate()")

    def on_account(self, accountupdate):
        account = accountupdate.account
        for botname, bot in self.config["bots"].items():
            if self.bots[botname].disabled:
                self.bots[botname].log.info("bot disabled" % botname)
                continue
            if bot["account"] == account["name"]:
                try:
                    self.bots[botname].onAccount(accountupdate)
                except Exception as e:
                    self.bots[botname].error_onAccount(e)
                    self.bots[botname].log.exception(".onAccountUpdate()")

    def run(self):
        self.init_bots()
        self.notify.listen()

    def stop(self, *args):
        self.notify.websocket.close()

    def remove_bot(self):
        for bot in self.bots:
            self.bots[bot].purge()

    @staticmethod
    def remove_offline_bot(config, bot_name):
        # Initialize the base strategy to get control over the data
        strategy = BaseStrategy(config, bot_name)
        strategy.purge()

    def report_now(self):
        """Force-generate a report if we can"""
        if self.reporter is not None:
            self.reporter.run_report_week()
        else:
            log.warn("No reporter available")

    def do_next_tick(self, job):
        """Add a callable to be executed on the next tick"""
        self.jobs.add(job)

    def reread_config(self):
        log.warn("reread_config not implemented yet")
Example #12
0
class WorkerInfrastructure(threading.Thread):
    def __init__(self, config, bitshares_instance=None, view=None):
        super().__init__()

        # BitShares instance
        self.bitshares = bitshares_instance or shared_bitshares_instance()
        self.block_time = None
        self.config = copy.deepcopy(config)
        self.view = view
        self.jobs = set()
        self.notify = None
        self.config_lock = threading.RLock()
        self.workers = {}

        self.accounts = set()
        self.markets = set()

        # Set the module search path
        user_worker_path = os.path.expanduser("~/bots")
        if os.path.exists(user_worker_path):
            sys.path.append(user_worker_path)

    def init_workers(self, config):
        """Initialize the workers."""
        self.config_lock.acquire()
        for worker_name, worker in config["workers"].items():
            if "account" not in worker:
                log_workers.critical(
                    "Worker has no account",
                    extra={
                        'worker_name': worker_name,
                        'account': 'unknown',
                        'market': 'unknown',
                        'is_disabled': (lambda: True),
                    },
                )
                continue
            if "market" not in worker:
                log_workers.critical(
                    "Worker has no market",
                    extra={
                        'worker_name': worker_name,
                        'account': worker['account'],
                        'market': 'unknown',
                        'is_disabled': (lambda: True),
                    },
                )
                continue
            try:
                strategy_class = getattr(
                    importlib.import_module(worker["module"]), 'Strategy')
                self.workers[worker_name] = strategy_class(
                    config=config,
                    name=worker_name,
                    bitshares_instance=self.bitshares,
                    view=self.view)
                self.markets.add(worker['market'])
                self.accounts.add(worker['account'])
            except BaseException:
                log_workers.exception(
                    "Worker initialisation",
                    extra={
                        'worker_name': worker_name,
                        'account': worker['account'],
                        'market': 'unknown',
                        'is_disabled': (lambda: True),
                    },
                )
        self.config_lock.release()

    def update_notify(self):
        if not self.config['workers']:
            log.critical("No workers configured to launch, exiting")
            raise errors.NoWorkersAvailable()
        if not self.workers:
            log.critical("No workers actually running")
            raise errors.NoWorkersAvailable()
        if self.notify:
            # Update the notification instance
            self.notify.reset_subscriptions(list(self.accounts),
                                            list(self.markets))
        else:
            # Initialize the notification instance
            self.notify = Notify(
                markets=list(self.markets),
                accounts=list(self.accounts),
                on_market=self.on_market,
                on_account=self.on_account,
                on_block=self.on_block,
                bitshares_instance=self.bitshares,
            )

    # Events
    def on_block(self, data):
        if self.jobs:
            try:
                for job in self.jobs:
                    job()
            finally:
                self.jobs = set()

        self.check_node_time()

        self.config_lock.acquire()
        for worker_name, worker in self.config["workers"].items():
            if worker_name not in self.workers:
                continue
            elif self.workers[worker_name].disabled:
                self.workers[worker_name].log.error(
                    'Worker "{}" is disabled'.format(worker_name))
                self.workers.pop(worker_name)
                continue
            try:
                self.workers[worker_name].ontick(data)
            except Exception as e:
                self.workers[worker_name].log.exception("in ontick()")
                try:
                    self.workers[worker_name].error_ontick(e)
                except Exception:
                    self.workers[worker_name].log.exception(
                        "in error_ontick()")
            finally:
                self.bitshares.txbuffer.clear()
        self.config_lock.release()

    def on_market(self, data):
        if data.get("deleted", False):  # No info available on deleted orders
            return

        self.config_lock.acquire()
        for worker_name, worker in self.config["workers"].items():
            if worker_name not in self.workers:
                continue
            elif self.workers[worker_name].disabled:
                self.workers[worker_name].log.error(
                    'Worker "{}" is disabled'.format(worker_name))
                self.workers.pop(worker_name)
                continue
            if worker["market"] == data.market:
                try:
                    self.workers[worker_name].onMarketUpdate(data)
                except Exception as e:
                    self.workers[worker_name].log.exception(
                        "in onMarketUpdate()")
                    try:
                        self.workers[worker_name].error_onMarketUpdate(e)
                    except Exception:
                        self.workers[worker_name].log.exception(
                            "in error_onMarketUpdate()")
                finally:
                    self.bitshares.txbuffer.clear()

        self.config_lock.release()

    def on_account(self, account_update):
        self.config_lock.acquire()
        account = account_update.account
        for worker_name, worker in self.config["workers"].items():
            if worker_name not in self.workers:
                continue
            elif self.workers[worker_name].disabled:
                self.workers[worker_name].log.error(
                    'Worker "{}" is disabled'.format(worker_name))
                self.workers.pop(worker_name)
                continue
            if worker["account"] == account["name"]:
                try:
                    self.workers[worker_name].onAccount(account_update)
                except Exception as e:
                    self.workers[worker_name].log.exception(
                        "in onAccountUpdate()")
                    try:
                        self.workers[worker_name].error_onAccount(e)
                    except Exception:
                        self.workers[worker_name].log.exception(
                            "in error_onAccountUpdate()")
                finally:
                    self.bitshares.txbuffer.clear()
        self.config_lock.release()

    def add_worker(self, worker_name, config):
        with self.config_lock:
            self.config['workers'][worker_name] = config['workers'][
                worker_name]
            self.init_workers(config)
        self.update_notify()

    def run(self):
        self.init_workers(self.config)
        self.update_notify()
        self.notify.listen()

    def check_node_time(self):
        """Check that we're connected to synced node."""
        props = self.bitshares.info()
        current_time = parse_time(props['time'])

        if not self.block_time:
            self.block_time = current_time
        elif current_time < self.block_time:
            current_node = self.bitshares.rpc.url
            self.bitshares.rpc.next()
            new_node = self.bitshares.rpc.url
            log.warning('Current node out of sync, switching: {} -> {}'.format(
                current_node, new_node))
            return
        else:
            self.block_time = current_time

    def stop(self, worker_name=None, pause=False):
        """
        Used to stop the worker(s)

        :param str worker_name: name of the worker to stop
        :param bool pause: optional argument which tells worker if it was stopped or just paused
        """
        if worker_name:
            try:
                # Kill only the specified worker
                self.remove_market(worker_name)
            except KeyError:
                # Worker was not found meaning it does not exist or it is paused already
                return

            with self.config_lock:
                account = self.config['workers'][worker_name]['account']
                self.config['workers'].pop(worker_name)

            # We should remove account subscription only if account is not used by another worker
            account_is_in_use = False
            for _, worker in self.workers.items():
                if worker.account.name == account:
                    account_is_in_use = True

            if not account_is_in_use:
                self.accounts.remove(account)
            if pause and worker_name in self.workers:
                self.workers[worker_name].pause()
            self.workers.pop(worker_name, None)
        else:
            # Kill all of the workers
            if pause:
                for worker in self.workers:
                    self.workers[worker].pause()
                self.workers = []

        # Update other workers
        if len(self.workers) > 0:
            self.update_notify()
        else:
            # No workers left, close websocket
            self.notify.websocket.close()

    def remove_worker(self, worker_name=None):
        if worker_name:
            self.workers[worker_name].purge()
        else:
            for worker in self.workers:
                self.workers[worker].purge()

    def remove_market(self, worker_name):
        """Remove the market only if the worker is the only one using it."""
        with self.config_lock:
            market = self.config['workers'][worker_name]['market']
            for name, worker in self.config['workers'].items():
                if market == worker['market']:
                    break  # Found the same market, do nothing
            else:
                # No markets found, safe to remove
                self.markets.remove(market)

    @staticmethod
    def remove_offline_worker(config, worker_name, bitshares_instance):
        # Initialize the base strategy to get control over the data
        strategy = StrategyBase(worker_name,
                                config,
                                bitshares_instance=bitshares_instance)
        strategy.clear_all_worker_data()

    @staticmethod
    def remove_offline_worker_data(worker_name):
        StrategyBase.purge_all_local_worker_data(worker_name)

    def do_next_tick(self, job):
        """Add a callable to be executed on the next tick."""
        self.jobs.add(job)