Example #1
0
    def run(self):
        # Initialize miners.
        logging.info('Querying NiceHash for miner connection information...')
        payrates = stratums = None
        while payrates is None:
            try:
                payrates = nicehash.simplemultialgo_info(self._settings)
                stratums = nicehash.stratums(self._settings)
            except Exception as err:
                logging.warning(
                    f'NiceHash stats: {err}, retrying in 5 seconds')
                time.sleep(5)
            else:
                self._payrates = (payrates, datetime.now())
        for miner in self._miners:
            miner.stratums = stratums
            miner.load()
        self._algorithms = sum([miner.algorithms for miner in self._miners],
                               [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        self._scheduler.enter(0, MiningSession.PROFIT_PRIORITY,
                              self._switch_algos)
        self._scheduler.run()
Example #2
0
    def run(self):
        # Initialize miners.
        logging.info('Querying NiceHash for miner connection information...')
        payrates = stratums = None
        while payrates is None:
            try:
                payrates, stratums = nicehash.simplemultialgo_info(self._settings)
            except (socket.error, socket.timeout, SSLError, URLError):
                time.sleep(5)
            else:
                self._last_payrates = (payrates, datetime.now())
        for miner in self._miners:
            miner.stratums = stratums
            miner.load()
        self._algorithms = sum([miner.algorithms for miner in self._miners], [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        # Attach the SIGINT signal for quitting.
        signal.signal(signal.SIGINT, lambda signum, frame: self.stop())

        self._scheduler.enter(0, MiningSession.PROFIT_PRIORITY, self._switch_algos)
        self._scheduler.run()
Example #3
0
    def run(self):
        # Initialize miners.
        stratums = None
        while stratums is None:
            try:
                payrates = nicehash.simplemultialgo_info(self._settings)
                stratums = nicehash.stratums(self._settings)
            except Exception as err:
                logging.warning(
                    f'NiceHash stats: {err}, retrying in 5 seconds')
                time.sleep(5)
            else:
                self._payrates = payrates
        self._miners = [miner(main.CONFIG_DIR) for miner in all_miners]
        for miner in self._miners:
            miner.settings = self._settings
            miner.stratums = stratums
        self._algorithms = sum([miner.algorithms for miner in self._miners],
                               [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        self._scheduler.enter(0, MiningThread.PROFIT_PRIORITY,
                              self._switch_algos)
        self._scheduler.enter(0, MiningThread.STATUS_PRIORITY,
                              self._read_status)
        self._scheduler.run()
Example #4
0
    def setUp(self):
        settings = nuxhash.settings.DEFAULT_SETTINGS
        settings['switching']['threshold'] = 0.5

        self.devices = tests.get_test_devices()
        self.benchmarks = tests.get_test_benchmarks()
        self.miner = Excavator(Path('/'), settings)
        self.equihash = next(a for a in self.miner.algorithms
                             if a.algorithms == ['equihash'])
        self.neoscrypt = next(a for a in self.miner.algorithms
                              if a.algorithms == ['neoscrypt'])

        self.switcher = NaiveSwitcher(settings)
        self.switcher.reset()
Example #5
0
def do_mining(nx_miners, nx_settings, nx_benchmarks, nx_devices):
    # Initialize miners.
    logging.info('Querying NiceHash for miner connection information...')
    mbtc_per_hash = download_time = stratums = None
    while mbtc_per_hash is None:
        try:
            mbtc_per_hash, stratums = nicehash.simplemultialgo_info(nx_settings)
        except (socket.error, socket.timeout, SSLError, URLError):
            sleep(5)
        else:
            download_time = datetime.now()
    for miner in nx_miners:
        miner.stratums = stratums
    algorithms = sum([miner.algorithms for miner in nx_miners], [])

    # Initialize profit-switching.
    profit_switch = NaiveSwitcher(nx_settings)
    profit_switch.reset()

    # SIGINT signal for quitting.
    quit = Event()
    signal.signal(signal.SIGINT, lambda signum, frame: quit.set())

    current_algorithm = {d: None for d in nx_devices}
    revenues = {d: defaultdict(lambda: 0.0) for d in nx_devices}
    while not quit.is_set():
        # Calculate BTC/day rates.
        def revenue(device, algorithm):
            benchmarks = nx_benchmarks[device]
            if algorithm.name in benchmarks:
                return sum([mbtc_per_hash[algorithm.algorithms[i]]
                            *benchmarks[algorithm.name][i]]
                            for i in range(len(algorithm.algorithms))])
            else:
                return 0.0
        revenues = {device: {algorithm: revenue(device, algorithm)
                             for algorithm in algorithms}
                    for device in nx_devices}

        # Get device -> algorithm assignments from profit switcher.
        current_algorithm = profit_switch.decide(revenues, download_time)
        for this_algorithm in algorithms:
            this_devices = [device for device, algorithm
                            in current_algorithm.items()
                            if algorithm == this_algorithm]
            this_algorithm.set_devices(my_devices)
Example #6
0
    def run(self):
        # Initialize miners.
        stratums = None
        while stratums is None:
            try:
                payrates, stratums = nicehash.simplemultialgo_info(
                    self._settings)
            except (socket.error, socket.timeout, SSLError, URLError):
                time.sleep(5)
        self._miners = [miner(main.CONFIG_DIR) for miner in all_miners]
        for miner in self._miners:
            miner.settings = self._settings
            miner.stratums = stratums
        self._algorithms = sum([miner.algorithms for miner in self._miners], [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        self._scheduler.enter(0, MiningThread.PROFIT_PRIORITY, self._switch_algos)
        self._scheduler.enter(0, MiningThread.STATUS_PRIORITY, self._read_status)
        self._scheduler.run()
Example #7
0
class TestNaiveSwitcher(TestCase):
    def setUp(self):
        settings = nuxhash.settings.DEFAULT_SETTINGS
        settings['switching']['threshold'] = 0.5

        self.devices = tests.get_test_devices()
        self.benchmarks = tests.get_test_benchmarks()
        self.miner = Excavator(Path('/'), settings)
        self.equihash = next(a for a in self.miner.algorithms
                             if a.algorithms == ['equihash'])
        self.neoscrypt = next(a for a in self.miner.algorithms
                              if a.algorithms == ['neoscrypt'])

        self.switcher = NaiveSwitcher(settings)
        self.switcher.reset()

    def test_most_profitable(self):
        device = self.devices[0]

        equihash_revenue = 4.0 * self.benchmarks[device]['excavator_equihash'][
            0]
        neoscrypt_revenue = 2.0 * self.benchmarks[device][
            'excavator_neoscrypt'][0]
        revenues = {
            device: {
                self.equihash: equihash_revenue,
                self.neoscrypt: neoscrypt_revenue
            }
        }
        decision = self.switcher.decide(revenues, None)

        self.assertEqual(decision[device], self.neoscrypt)

    def test_below_threshold(self):
        device = self.devices[0]

        self.switcher.decide(
            {device: {
                self.equihash: 2.0,
                self.neoscrypt: 1.0
            }}, None)
        decision = self.switcher.decide(
            {device: {
                self.equihash: 2.0,
                self.neoscrypt: 2.5
            }}, None)

        self.assertEqual(decision[device], self.equihash)

    def test_above_threshold(self):
        device = self.devices[0]

        self.switcher.decide(
            {device: {
                self.equihash: 2.0,
                self.neoscrypt: 1.0
            }}, None)
        decision = self.switcher.decide(
            {device: {
                self.equihash: 2.0,
                self.neoscrypt: 3.5
            }}, None)

        self.assertEqual(decision[device], self.neoscrypt)
Example #8
0
class MiningSession(object):

    PROFIT_PRIORITY = 1
    STOP_PRIORITY = 0

    def __init__(self, miners, settings, benchmarks, devices):
        self._miners = miners
        self._settings = settings
        self._benchmarks = benchmarks
        self._devices = devices
        self._payrates = (None, None)
        self._quit_signal = Event()
        self._scheduler = sched.scheduler(time.time,
                                          lambda t: self._quit_signal.wait(t))
        self._algorithms = []
        self._profit_switch = None

    def run(self):
        # Initialize miners.
        logging.info('Querying NiceHash for miner connection information...')
        payrates = stratums = None
        while payrates is None:
            try:
                payrates = nicehash.simplemultialgo_info(self._settings)
                stratums = nicehash.stratums(self._settings)
            except Exception as err:
                logging.warning(
                    f'NiceHash stats: {err}, retrying in 5 seconds')
                time.sleep(5)
            else:
                self._payrates = (payrates, datetime.now())
        for miner in self._miners:
            miner.stratums = stratums
            miner.load()
        self._algorithms = sum([miner.algorithms for miner in self._miners],
                               [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        self._scheduler.enter(0, MiningSession.PROFIT_PRIORITY,
                              self._switch_algos)
        self._scheduler.run()

    def stop(self):
        self._scheduler.enter(0, MiningSession.STOP_PRIORITY,
                              self._stop_mining)
        self._quit_signal.set()

    def _switch_algos(self):
        # Get profitability information from NiceHash.
        try:
            ret_payrates = nicehash.simplemultialgo_info(self._settings)
        except Exception as err:
            logging.warning(f'NiceHash stats: {err}')
        else:
            self._payrates = (ret_payrates, datetime.now())

        interval = self._settings['switching']['interval']
        payrates, payrates_time = self._payrates

        # Calculate BTC/day rates.
        def revenue(device, algorithm):
            benchmarks = self._benchmarks[device]
            if algorithm.name in benchmarks:
                return sum([
                    payrates[sub_algo] * benchmarks[algorithm.name][i]
                    if sub_algo in payrates else 0.0
                    for i, sub_algo in enumerate(algorithm.algorithms)
                ])
            else:
                return 0.0

        revenues = {
            device: {
                algorithm: revenue(device, algorithm)
                for algorithm in self._algorithms
            }
            for device in self._devices
        }

        # Get device -> algorithm assignments from profit switcher.
        self._assignments = self._profit_switch.decide(revenues, payrates_time)
        for this_algorithm in self._algorithms:
            this_devices = [
                device for device, algorithm in self._assignments.items()
                if algorithm == this_algorithm
            ]
            this_algorithm.set_devices(this_devices)

        # Donation time.
        if not self._settings['donate']['optout'] and random() < DONATE_PROB:
            logging.warning('This interval will be donation time.')
            donate_settings = deepcopy(self._settings)
            donate_settings['nicehash']['wallet'] = DONATE_ADDRESS
            donate_settings['nicehash']['workername'] = 'nuxhash'
            for miner in self._miners:
                miner.settings = donate_settings
            self._scheduler.enter(interval, MiningSession.PROFIT_PRIORITY,
                                  self._reset_miners)

        self._scheduler.enter(interval, MiningSession.PROFIT_PRIORITY,
                              self._switch_algos)

    def _reset_miners(self):
        for miner in self._miners:
            miner.settings = self._settings

    def _stop_mining(self):
        logging.info('Quit signal received, terminating miners')
        # Empty the scheduler.
        for job in self._scheduler.queue:
            self._scheduler.cancel(job)
Example #9
0
class MiningSession(object):

    PROFIT_PRIORITY = 1
    STOP_PRIORITY = 0

    def __init__(self, miners, settings, benchmarks, devices):
        self._miners = miners
        self._settings = settings
        self._benchmarks = benchmarks
        self._devices = devices
        self._payrates = (None, None)
        self._quit_signal = Event()
        self._scheduler = sched.scheduler(time.time,
                                          lambda t: self._quit_signal.wait(t))
        self._algorithms = []
        self._profit_switch = None

    def run(self):
        # Initialize miners.
        logging.info('Querying NiceHash for miner connection information...')
        payrates = stratums = None
        while payrates is None:
            try:
                payrates, stratums = nicehash.simplemultialgo_info(
                    self._settings)
            except NH_EXCEPTIONS as err:
                logging.warning('NiceHash stats: %s, retrying in 5 seconds' %
                                err)
                time.sleep(5)
            else:
                self._payrates = (payrates, datetime.now())
        for miner in self._miners:
            miner.stratums = stratums
            miner.load()
        self._algorithms = sum([miner.algorithms for miner in self._miners],
                               [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        # Attach the SIGINT signal for quitting.
        # NOTE: If running in a shell, Ctrl-C will get sent to our subprocesses too,
        #       because we are the foreground process group. Miners will get killed
        #       before we have a chance to properly shut them down.
        signal.signal(signal.SIGINT, lambda signum, frame: self.stop())

        self._scheduler.enter(0, MiningSession.PROFIT_PRIORITY,
                              self._switch_algos)
        self._scheduler.run()

    def stop(self):
        self._scheduler.enter(0, MiningSession.STOP_PRIORITY,
                              self._stop_mining)
        self._quit_signal.set()

    def _switch_algos(self):
        # Get profitability information from NiceHash.
        try:
            ret_payrates, stratums = nicehash.simplemultialgo_info(
                self._settings)
        except NH_EXCEPTIONS as err:
            logging.warning('NiceHash stats: %s' % err)
        else:
            self._payrates = (ret_payrates, datetime.now())

        interval = self._settings['switching']['interval']
        payrates, payrates_time = self._payrates

        # Calculate BTC/day rates.
        def revenue(device, algorithm):
            benchmarks = self._benchmarks[device]
            if algorithm.name in benchmarks:
                return sum([
                    payrates[algorithm.algorithms[i]] *
                    benchmarks[algorithm.name][i]
                    for i in range(len(algorithm.algorithms))
                ])
            else:
                return 0.0

        revenues = {
            device: {
                algorithm: revenue(device, algorithm)
                for algorithm in self._algorithms
            }
            for device in self._devices
        }

        # Get device -> algorithm assignments from profit switcher.
        self._assignments = self._profit_switch.decide(revenues, payrates_time)
        for this_algorithm in self._algorithms:
            this_devices = [
                device for device, algorithm in self._assignments.items()
                if algorithm == this_algorithm
            ]
            this_algorithm.set_devices(this_devices)

        # Donation time.
        if not self._settings['donate']['optout'] and random() < DONATE_PROB:
            logging.warning('This interval will be donation time.')
            donate_settings = deepcopy(self._settings)
            donate_settings['nicehash']['wallet'] = DONATE_ADDRESS
            donate_settings['nicehash']['workername'] = 'nuxhash'
            for miner in self._miners:
                miner.settings = donate_settings
            self._scheduler.enter(interval, MiningSession.PROFIT_PRIORITY,
                                  self._reset_miners)

        self._scheduler.enter(interval, MiningSession.PROFIT_PRIORITY,
                              self._switch_algos)

    def _reset_miners(self):
        for miner in self._miners:
            miner.settings = self._settings

    def _stop_mining(self):
        logging.info('Cleaning up')
        for algorithm in self._algorithms:
            algorithm.set_devices([])
        for miner in self._miners:
            miner.unload()
        # Empty the scheduler.
        for job in self._scheduler.queue:
            self._scheduler.cancel(job)
Example #10
0
class MiningThread(threading.Thread):

    PROFIT_PRIORITY = 1
    STATUS_PRIORITY = 2
    STOP_PRIORITY = 0

    def __init__(self,
                 devices=[],
                 window=None,
                 settings=DEFAULT_SETTINGS,
                 benchmarks=EMPTY_BENCHMARKS):
        threading.Thread.__init__(self)
        self._window = window
        self._settings = settings
        self._benchmarks = benchmarks
        self._devices = devices
        self._stop_signal = threading.Event()
        self._scheduler = sched.scheduler(time.time,
                                          lambda t: self._stop_signal.wait(t))

    def run(self):
        # Initialize miners.
        stratums = None
        while stratums is None:
            try:
                payrates, stratums = nicehash.simplemultialgo_info(
                    self._settings)
            except (socket.error, socket.timeout, SSLError, URLError):
                time.sleep(5)
        self._miners = [miner(main.CONFIG_DIR) for miner in all_miners]
        for miner in self._miners:
            miner.settings = self._settings
            miner.stratums = stratums
        self._algorithms = sum([miner.algorithms for miner in self._miners],
                               [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        self._scheduler.enter(0, MiningThread.PROFIT_PRIORITY,
                              self._switch_algos)
        self._scheduler.enter(0, MiningThread.STATUS_PRIORITY,
                              self._read_status)
        self._scheduler.run()

    def stop(self):
        self._scheduler.enter(0, MiningThread.STOP_PRIORITY, self._stop_mining)
        self._stop_signal.set()
        self.join()

    def _switch_algos(self):
        interval = self._settings['switching']['interval']

        # Get profitability information from NiceHash.
        try:
            payrates, stratums = nicehash.simplemultialgo_info(self._settings)
        except (socket.error, socket.timeout, SSLError, URLError) as err:
            logging.warning('NiceHash stats: %s' % err)
        except nicehash.BadResponseError:
            logging.warning('NiceHash stats: Bad response')
        else:
            download_time = datetime.now()
            self._current_payrates = payrates

        # Calculate BTC/day rates.
        def revenue(device, algorithm):
            benchmarks = self._benchmarks[device]
            if algorithm.name in benchmarks:
                return sum([
                    payrates[algorithm.algorithms[i]] *
                    benchmarks[algorithm.name][i]
                    for i in range(len(algorithm.algorithms))
                ])
            else:
                return 0.0

        revenues = {
            device: {
                algorithm: revenue(device, algorithm)
                for algorithm in self._algorithms
            }
            for device in self._devices
        }

        # Get device -> algorithm assignments from profit switcher.
        assigned_algorithm = self._profit_switch.decide(
            revenues, download_time)
        self._assignments = assigned_algorithm
        for this_algorithm in self._algorithms:
            this_devices = [
                device for device, algorithm in assigned_algorithm.items()
                if algorithm == this_algorithm
            ]
            this_algorithm.set_devices(this_devices)

        # Donation time.
        if not self._settings['donate']['optout'] and random() < DONATE_PROB:
            logging.warning('This interval will be donation time.')
            donate_settings = deepcopy(self._settings)
            donate_settings['nicehash']['wallet'] = DONATE_ADDRESS
            donate_settings['nicehash']['workername'] = 'nuxhash'
            for miner in self._miners:
                miner.settings = donate_settings
            self._scheduler.enter(interval, MiningThread.PROFIT_PRIORITY,
                                  self._reset_miners)

        self._scheduler.enter(interval, MiningThread.PROFIT_PRIORITY,
                              self._switch_algos)

    def _reset_miners(self):
        for miner in self._miners:
            miner.settings = self._settings

    def _read_status(self):
        running_algorithms = self._assignments.values()
        speeds = {
            algorithm: algorithm.current_speeds()
            for algorithm in running_algorithms
        }
        revenue = {
            algorithm: sum([
                self._current_payrates[multialgorithm] * speeds[algorithm][i]
                for i, multialgorithm in enumerate(algorithm.algorithms)
            ])
            for algorithm in running_algorithms
        }
        devices = {
            algorithm: [
                device for device, this_algorithm in self._assignments.items()
                if this_algorithm == algorithm
            ]
            for algorithm in running_algorithms
        }
        main.sendMessage(self._window,
                         'mining.status',
                         speeds=speeds,
                         revenue=revenue,
                         devices=devices)
        self._scheduler.enter(MINING_UPDATE_SECS, MiningThread.STATUS_PRIORITY,
                              self._read_status)

    def _stop_mining(self):
        logging.info('Stopping mining')
        for algorithm in self._algorithms:
            algorithm.set_devices([])
        for miner in self._miners:
            miner.unload()
        # Empty the scheduler.
        for job in self._scheduler.queue:
            self._scheduler.cancel(job)
Example #11
0
class MiningThread(threading.Thread):

    PROFIT_PRIORITY = 1
    STATUS_PRIORITY = 2
    STOP_PRIORITY = 0

    def __init__(self, window, settings, benchmarks, devices):
        threading.Thread.__init__(self)
        self._window = window
        self._settings = settings
        self._benchmarks = benchmarks
        self._devices = devices
        self._stop_signal = threading.Event()
        self._scheduler = sched.scheduler(time.time,
                                          lambda t: self._stop_signal.wait(t))

    def run(self):
        # Initialize miners.
        stratums = None
        while stratums is None:
            try:
                payrates, stratums = nicehash.simplemultialgo_info(
                    self._settings)
            except (socket.error, socket.timeout, SSLError, URLError):
                time.sleep(5)
        self._miners = [Excavator(CONFIG_DIR, self._settings)]
        for miner in self._miners:
            miner.stratums = stratums
        self._algorithms = sum([miner.algorithms for miner in self._miners],
                               [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        self._scheduler.enter(0, MiningThread.PROFIT_PRIORITY,
                              self._switch_algos)
        self._scheduler.enter(0, MiningThread.STATUS_PRIORITY,
                              self._read_status)
        self._scheduler.run()

    def stop(self):
        self._scheduler.enter(0, MiningThread.STOP_PRIORITY, self._stop_mining)
        self._stop_signal.set()

    def _switch_algos(self):
        # Get profitability information from NiceHash.
        try:
            payrates, stratums = nicehash.simplemultialgo_info(self._settings)
        except (socket.error, socket.timeout, SSLError, URLError) as err:
            logging.warning('NiceHash stats: %s' % err)
        except nicehash.BadResponseError:
            logging.warning('NiceHash stats: Bad response')
        else:
            download_time = datetime.now()
            self._current_payrates = payrates

        # Calculate BTC/day rates.
        def revenue(device, algorithm):
            benchmarks = self._benchmarks[device]
            if algorithm.name in benchmarks:
                return sum([
                    payrates[algorithm.algorithms[i]] *
                    benchmarks[algorithm.name][i]
                    for i in range(len(algorithm.algorithms))
                ])
            else:
                return 0.0

        revenues = {
            device: {
                algorithm: revenue(device, algorithm)
                for algorithm in self._algorithms
            }
            for device in self._devices
        }

        # Get device -> algorithm assignments from profit switcher.
        assigned_algorithm = self._profit_switch.decide(
            revenues, download_time)
        self._assignments = assigned_algorithm
        for this_algorithm in self._algorithms:
            this_devices = [
                device for device, algorithm in assigned_algorithm.items()
                if algorithm == this_algorithm
            ]
            this_algorithm.set_devices(this_devices)

        self._scheduler.enter(self._settings['switching']['interval'],
                              MiningThread.PROFIT_PRIORITY, self._switch_algos)

    def _read_status(self):
        running_algorithms = self._assignments.values()
        # Check miner status.
        for algorithm in running_algorithms:
            if not algorithm.parent.is_running():
                logging.error('Detected %s crash, restarting miner' %
                              algorithm.name)
                algorithm.parent.reload()
        speeds = {
            algorithm: algorithm.current_speeds()
            for algorithm in running_algorithms
        }
        revenue = {
            algorithm: sum([
                self._current_payrates[multialgorithm] * speeds[algorithm][i]
                for i, multialgorithm in enumerate(algorithm.algorithms)
            ])
            for algorithm in running_algorithms
        }
        devices = {
            algorithm: [
                device for device, this_algorithm in self._assignments.items()
                if this_algorithm == algorithm
            ]
            for algorithm in running_algorithms
        }
        wx.PostEvent(
            self._window,
            MiningStatusEvent(speeds=speeds, revenue=revenue, devices=devices))
        self._scheduler.enter(MINING_UPDATE_SECS, MiningThread.STATUS_PRIORITY,
                              self._read_status)

    def _stop_mining(self):
        logging.info('Stopping mining')
        for algorithm in self._algorithms:
            algorithm.set_devices([])
        for miner in self._miners:
            miner.unload()
        # Empty the scheduler.
        for job in self._scheduler.queue:
            self._scheduler.cancel(job)
Example #12
0
class MiningSession(object):

    PROFIT_PRIORITY = 1
    WATCH_PRIORITY = 2
    STOP_PRIORITY = 0
    WATCH_INTERVAL = 15

    def __init__(self, miners, settings, benchmarks, devices):
        self._miners = miners
        self._settings = settings
        self._benchmarks = benchmarks
        self._devices = devices
        self._last_payrates = (None, None)
        self._quit_signal = Event()
        self._scheduler = sched.scheduler(time.time,
                                          lambda t: self._quit_signal.wait(t))
        self._algorithms = []
        self._profit_switch = None

    def run(self):
        # Initialize miners.
        logging.info('Querying NiceHash for miner connection information...')
        payrates = stratums = None
        while payrates is None:
            try:
                payrates, stratums = nicehash.simplemultialgo_info(
                    self._settings)
            except (socket.error, socket.timeout, SSLError, URLError):
                time.sleep(5)
            else:
                self._last_payrates = (payrates, datetime.now())
        for miner in self._miners:
            miner.stratums = stratums
            miner.load()
        self._algorithms = sum([miner.algorithms for miner in self._miners],
                               [])

        # Initialize profit-switching.
        self._profit_switch = NaiveSwitcher(self._settings)
        self._profit_switch.reset()

        # Attach the SIGINT signal for quitting.
        signal.signal(signal.SIGINT, lambda signum, frame: self.stop())

        self._scheduler.enter(0, MiningSession.PROFIT_PRIORITY,
                              self._switch_algos)
        self._scheduler.enter(0, MiningSession.WATCH_PRIORITY,
                              self._watch_algos)
        self._scheduler.run()

    def stop(self):
        self._scheduler.enter(0, MiningSession.STOP_PRIORITY,
                              self._stop_mining)
        self._quit_signal.set()

    def _switch_algos(self):
        interval = self._settings['switching']['interval']

        # Get profitability information from NiceHash.
        try:
            payrates, stratums = nicehash.simplemultialgo_info(self._settings)
        except (socket.error, socket.timeout, SSLError, URLError) as err:
            logging.warning('NiceHash stats: %s' % err)
        except nicehash.BadResponseError:
            logging.warning('NiceHash stats: Bad response')
        else:
            download_time = datetime.now()
            self._last_payrates = (payrates, download_time)

        # Calculate BTC/day rates.
        def revenue(device, algorithm):
            benchmarks = self._benchmarks[device]
            if algorithm.name in benchmarks:
                return sum([
                    payrates[algorithm.algorithms[i]] *
                    benchmarks[algorithm.name][i]
                    for i in range(len(algorithm.algorithms))
                ])
            else:
                return 0.0

        revenues = {
            device: {
                algorithm: revenue(device, algorithm)
                for algorithm in self._algorithms
            }
            for device in self._devices
        }

        # Get device -> algorithm assignments from profit switcher.
        self._assignments = self._profit_switch.decide(revenues, download_time)
        for this_algorithm in self._algorithms:
            this_devices = [
                device for device, algorithm in self._assignments.items()
                if algorithm == this_algorithm
            ]
            this_algorithm.set_devices(this_devices)

        # Donation time.
        if not self._settings['donate']['optout'] and random() < DONATE_PROB:
            logging.warning('This interval will be donation time.')
            donate_settings = deepcopy(self._settings)
            donate_settings['nicehash']['wallet'] = DONATE_ADDRESS
            donate_settings['nicehash']['workername'] = 'nuxhash'
            for miner in self._miners:
                miner.settings = donate_settings
            self._scheduler.enter(interval, MiningSession.PROFIT_PRIORITY,
                                  self._reset_miners)

        self._scheduler.enter(interval, MiningSession.PROFIT_PRIORITY,
                              self._switch_algos)

    def _reset_miners(self):
        for miner in self._miners:
            miner.settings = self._settings

    def _watch_algos(self):
        running_algorithms = self._assignments.values()
        for algorithm in running_algorithms:
            if not algorithm.parent.is_running():
                logging.error('Detected %s crash, restarting miner' %
                              algorithm.name)
                algorithm.parent.reload()
        self._scheduler.enter(MiningSession.WATCH_INTERVAL,
                              MiningSession.WATCH_PRIORITY, self._watch_algos)

    def _stop_mining(self):
        logging.info('Cleaning up')
        for algorithm in self._algorithms:
            algorithm.set_devices([])
        for miner in self._miners:
            miner.unload()
        # Empty the scheduler.
        for job in self._scheduler.queue:
            self._scheduler.cancel(job)