Exemplo n.º 1
0
    def __init__(self, data=None, flavor='mysql', lvl='debug'):
        #NOTE Allowing different data access ?
        self.data          = data
        if not data:
            self.feeds         = DataFeed()
        self.backtest_cfg  = None
        self.algo_cfg      = None
        self.manager_cfg   = None
        self.monthly_perfs = None

        self.server        = ZMQ_Dealer(id=self.__class__.__name__)
Exemplo n.º 2
0
def genetic(domain, cost_obj, popsize=50, step=1,
                     mutprob=0.2, elite=0.2, maxiter=100, stop=0, notify_android=False):
    '''
    Parameter optimization using genetic algorithm
    ______________________________________________
    Parameters
        domain: vec(...) of tuple(2)
            define range for each parameter
        cost_obj: Metric(1)
            compute the score of a solution
        popsize: int(1)
            number of solution set in one generation
        step: float(1)
            sensibility used while mutating a parameter
        mutprob: float(1)
            probability for a solution to mutate
        elite: float(1)
            % of best chromosomes selected
        maxiter: int(1)
            maximum number of population evolution
        stop: float(1)
            stop the algorithm when the fitness function reachs this value
        notify_android: bool(1)
            flag that let you send a notificatioin on android device when the algo is done
    ______________________________________________
    Return
        scores[0][0]: float(1)
            Best score the algorithm reached
        scores[0][1]: vec(...) of float(1)
           parameters that gave the best score
    '''
    # Initialisation
    client = ZMQ_Dealer(id=genetic.__name__)
    client.run(host='127.0.0.1', port=5570)
    check_buffer = [1] * 4

    # Mutation Operation
    def mutate(vec):
        i = random.randint(0, len(domain) - 1)
        if random.random() < 0.5 and vec[i] > domain[i][0]:
            mutated_param = vec[i] - step if vec[i] - step >= domain[i][0] else domain[i][0]
        elif vec[i] < domain[i][1]:
            mutated_param = vec[i] + step if vec[i] + step <= domain[i][0] else domain[i][0]
        return vec[0:i] + [mutated_param] + vec[i + 1:]

    # Crossover Operation
    def crossover(r1, r2):
        i = random.randint(1, len(domain) - 2)
        return r1[0:i] + r2[i:]

    def should_stop(best):
        ''' Break the loop if no longer evolution, or reached stop criteria '''
        check_buffer.append(best)
        check_buffer.pop(0)
        return (best >= check_buffer[0]) or (best <= stop)

    log.info('Build the initial population')
    pop = []
    for i in range(popsize):
        vec = [random.randint(domain[i][0], domain[i][1])
               for i in range(len(domain))]
        pop.append(vec)

    # How many winners from each generation?
    topelite = int(elite * popsize)

    log.info('Run main loop')
    for i in range(maxiter):
        log.info('Rank population')
        scores = [(cost_obj.fitness(v), v) for v in pop]
        scores.sort()
        ranked = [v for (s, v) in scores]

        # Start with the pure winners
        log.info('Select elite')
        pop = ranked[0:topelite]

        # Add mutated and bred forms of the winners
        log.info('Evolve loosers')
        while len(pop) < popsize:
            if random.random() < mutprob:
                log.debug('Process mutation')
                c = random.randint(0, topelite)
                pop.append(mutate(ranked[c]))
            else:
                log.debug('Process crossover')
                c1 = random.randint(0, topelite)
                c2 = random.randint(0, topelite)
                pop.append(crossover(ranked[c1], ranked[c2]))

        #TODO add worst
        log.error(scores)
        log.notice('Best score so far: {}'.format(scores[0][0]))
        client.send({'best': scores[0],
                     'worst': scores[-1],
                     'parameters': scores[0][1],
                     'mean': np.mean([s[0] for s in scores]),
                     'std': np.std([s[0] for s in scores]),
                     'iteration': i + 1,
                     'progress': round(float(i + 1) / float(maxiter), 2) * 100.0},
                     type='optimization',
                     channel='dashboard')
        if should_stop(scores[0][0]):
            log.info('Stop criteria reached, done with optimization.')
            break

    if notify_android:
        client.send_to_android({'title': 'Optimization is done',
                                'priority': 1,
                                'description': 'Genetic algorithm evolved the solution to {} \
                                               (with parameters {})'.format(scores[0][0], scores[0][1])})
    return scores[0][0], scores[0][1]
Exemplo n.º 3
0
class Simulation(object):
    ''' Take a trading strategie and evalute its results '''
    def __init__(self, data=None, flavor='mysql', lvl='debug'):
        #NOTE Allowing different data access ?
        self.data          = data
        if not data:
            self.feeds         = DataFeed()
        self.backtest_cfg  = None
        self.algo_cfg      = None
        self.manager_cfg   = None
        self.monthly_perfs = None

        self.server        = ZMQ_Dealer(id=self.__class__.__name__)

    def configure(self, bt_cfg=None, a_cfg=None, m_cfg=None):
        ''' Reads and provides a clean configuration for the simulation '''

        if not bt_cfg:
            parser = argparse.ArgumentParser(description='Backtester module, the terrific financial simukation')
            parser.add_argument('-v', '--version', action='version', version='%(prog)s v0.8.1 Licence rien du tout', help='Print program version')
            parser.add_argument('-d', '--delta', type=str, action='store', default='D', required=False, help='Delta in days betweend two quotes fetch')
            parser.add_argument('-a', '--algorithm', action='store', required=True, help='Trading algorithm to be used')
            parser.add_argument('-m', '--manager', action='store', required=True, help='Portfolio strategie to be used')
            parser.add_argument('-b', '--database', action='store', default='test', required=False, help='Table to considere in database')
            parser.add_argument('-t', '--tickers', action='store', required=True, help='target names to process')
            parser.add_argument('-s', '--start', action='store', default='1/1/2006', required=False, help='Start date of the backtester')
            parser.add_argument('-e', '--end', action='store', default='1/12/2010', required=False, help='Stop date of the backtester')
            parser.add_argument('-r', '--remote', action='store_true', help='Indicates if the program was ran manually or not')
            parser.add_argument('-l', '--live', action='store_true', help='makes the engine work in real-time !')
            parser.add_argument('-p', '--port', action='store', default=5570, required=False, help='Activates the diffusion of the universe on the network, on the port provided')
            args = parser.parse_args()

            self.backtest_cfg = {'algorithm'   : args.algorithm,
                                 'delta'       : args.delta,
                                 'manager'     : args.manager,
                                 'database'    : args.database,
                                 'tickers'     : args.tickers.split(','),
                                 'start'       : args.start,
                                 'end'         : args.end,
                                 'live'        : args.live,
                                 'port'        : args.port,
                                 'remote'      : args.remote}
        else:
            self.backtest_cfg = bt_cfg

        if isinstance(self.backtest_cfg['start'], str) and isinstance(self.backtest_cfg['end'], str):
            ###############test = pd.datetime.strptime('2012-02-01:14:00', '%Y-%m-%d:%H:%M')
            self.backtest_cfg['start'] = pytz.utc.localize(pd.datetime.strptime(self.backtest_cfg['start'], '%Y-%m-%d'))
            self.backtest_cfg['end']   = pytz.utc.localize(pd.datetime.strptime(self.backtest_cfg['end'], '%Y-%m-%d'))
        elif isinstance(self.backtest_cfg['start'], pd.datetime) and isinstance(self.backtest_cfg['end'], pd.datetime):
            if not self.backtest_cfg['start'].tzinfo:
                self.backtest_cfg['start'] = pytz.utc.localize(self.backtest_cfg['start'])
            if not self.backtest_cfg['end'].tzinfo:
                self.backtest_cfg['end'] = pytz.utc.localize(self.backtest_cfg['end'])
        else:
            raise NotImplementedError()

        #if self.backtest_cfg['interactive']:
        self.read_config(remote=self.backtest_cfg['remote'], manager=m_cfg, algorithm=a_cfg)
        #else:
            #self.server.run_forever(port=self.backtest_cfg['port'], on_recv=self.read_config)

        return self.backtest_cfg

    def read_config(self, *args, **kwargs):
        self.manager_cfg = kwargs.get('manager', None)
        self.algo_cfg = kwargs.get('algorithm', None)

        if kwargs.get('remote', False):
            self.server.run(host='127.0.0.1', port=self.backtest_cfg['port'])

            # In remote mode, client sends missing configuration through zmq socket
            if (not self.manager_cfg) or (not self.algo_cfg):
                log.info('Fetching backtest configuration from client')
                msg = self.server.receive(json=True)
                log.debug('Got it !')

            # Set simulation parameters with it
            assert isinstance(msg, dict)
            if self.manager_cfg is None:
                self.manager_cfg = msg['manager']
            if self.algo_cfg is None:
                self.algo_cfg    = msg['algorithm']

        else:
            # Reading configuration from json files
            config_dir = '/'.join((os.environ['QTRADE'], 'config/'))
            try:
                # Files store many algos and manager parameters,
                # use backtest configuration to pick up the right one
                if self.manager_cfg is None:
                    self.manager_cfg = json.load(open('{}/managers.cfg'.format(config_dir), 'r'))[self.backtest_cfg['manager']]
                if self.algo_cfg is None:
                    self.algo_cfg    = json.load(open('{}/algos.cfg'.format(config_dir), 'r'))[self.backtest_cfg['algorithm']]
            except:
                log.error('** loading json configuration.')
                sys.exit(1)

        # The manager can use the same socket during simulation to emit portfolio informations
        self.manager_cfg['server'] = self.server
        log.info('Configuration is Done.')

    def run_backtest(self):
        # No configuration, no backtest man
        if self.backtest_cfg is None or self.algo_cfg is None or self.manager_cfg is None:
            log.error('** Backtester not configured properly')
            return 1

        #'''_____________________________________________________________________    Parameters    ________
        if self.backtest_cfg['tickers'][0] == 'random':
            assert(len(self.backtest_cfg['tickers']) == 2)
            assert(int(self.backtest_cfg['tickers'][1]))
            self.backtest_cfg['tickers'] = self.feeds.random_stocks(int(self.backtest_cfg['tickers'][1]))

        if self.backtest_cfg['live']:
            #dates = pd.date_range(self.backtest_cfg['start'], self.backtest_cfg['end'], freq=self.backtest_cfg['delta'])
            #NOTE A temporary hack to avoid zipline dirty modification
            periods = self.backtest_cfg['end'] - self.backtest_cfg['start']
            dates = pd.date_range(pd.datetime.now(), periods=periods.days + 1, freq=self.backtest_cfg['delta'])
            '''
            if dates.freq > pd.datetools.Day():
                #fr_selector = ((dates.hour > 8) & (dates.hour < 17)) | ((dates.hour == 17) & (dates.minute < 31))
                us_selector = ((dates.hour > 15) & (dates.hour < 22)) | ((dates.hour == 15) & (dates.minute > 31))
                dates = dates[us_selector]
            if not dates:
                log.warning('! Market closed.')
                sys.exit(0)
            '''
            data = {'tickers': self.backtest_cfg['tickers'], 'index': dates.tz_localize(pytz.utc)}
        else:
            data = self.feeds.quotes(self.backtest_cfg['tickers'],
                                     start_date = self.backtest_cfg['start'],
                                     end_date   = self.backtest_cfg['end'])
            assert isinstance(data, pd.DataFrame)
            assert data.index.tzinfo

        #___________________________________________________________________________    Running    ________
        log.info('\n-- Running backetester...\nUsing algorithm: {}\n'.format(self.backtest_cfg['algorithm']))
        log.info('\n-- Using portfolio manager: {}\n'.format(self.backtest_cfg['manager']))

        backtester = BacktesterEngine(self.backtest_cfg['algorithm'],
                                      self.backtest_cfg['manager'],
                                      self.algo_cfg,
                                      self.manager_cfg)
        self.results, self.monthly_perfs = backtester.run(data)
                                                          #self.backtest_cfg['start'],
                                                          #self.backtest_cfg['end'])
        return self.results

    def rolling_performances(self, timestamp='one_month', save=False, db_id=None):
        ''' Filters self.perfs and, if asked, save it to database '''
        #TODO Study the impact of month choice
        #TODO Check timestamp in an enumeration

        #NOTE Script args define default database table name (test), make it consistent
        if db_id is None:
            db_id = self.backtest_cfg['algorithm'] + pd.datetime.strftime(pd.datetime.now(), format='%Y%m%d')

        if self.monthly_perfs:
            perfs  = dict()
            length = range(len(self.monthly_perfs[timestamp]))
            index  = self._get_index(self.monthly_perfs[timestamp])
            perfs['Name']                 = np.array([db_id] * len(self.monthly_perfs[timestamp]))
            #perfs['Period']               = np.array([self.monthly_perfs[timestamp][i]['period_label'] for i in length])
            perfs['Period']               = np.array([pd.datetime.date(date)                                      for date in index])
            perfs['Sharpe.Ratio']         = np.array([self.monthly_perfs[timestamp][i]['sharpe']                  for i in length])
            perfs['Returns']              = np.array([self.monthly_perfs[timestamp][i]['algorithm_period_return'] for i in length])
            perfs['Max.Drawdown']         = np.array([self.monthly_perfs[timestamp][i]['max_drawdown']            for i in length])
            perfs['Volatility']           = np.array([self.monthly_perfs[timestamp][i]['algo_volatility']         for i in length])
            perfs['Beta']                 = np.array([self.monthly_perfs[timestamp][i]['beta']                    for i in length])
            perfs['Alpha']                = np.array([self.monthly_perfs[timestamp][i]['alpha']                   for i in length])
            perfs['Excess.Returns']       = np.array([self.monthly_perfs[timestamp][i]['excess_return']           for i in length])
            perfs['Benchmark.Returns']    = np.array([self.monthly_perfs[timestamp][i]['benchmark_period_return'] for i in length])
            perfs['Benchmark.Volatility'] = np.array([self.monthly_perfs[timestamp][i]['benchmark_volatility']    for i in length])
            perfs['Treasury.Returns']     = np.array([self.monthly_perfs[timestamp][i]['treasury_period_return']  for i in length])
        else:
            #TODO Get it from DB if it exists
            raise NotImplementedError()

        try:
            data = pd.DataFrame(perfs, index=index)
        except:
            import ipdb; ipdb.set_trace()

        if save:
            self.feeds.stock_db.save_metrics(data)
        return data

    def overall_metrics(self, timestamp='one_month', save=False, db_id=None):
        ''' Use zipline results to compute some performance indicators and store it in database '''
        perfs = dict()
        metrics = self.rolling_performances(timestamp=timestamp, save=False, db_id=db_id)
        riskfree = np.mean(metrics['Treasury.Returns'])

        #NOTE Script args define default database table name (test), make it consistent
        if db_id is None:
            db_id = self.backtest_cfg['algorithm'] + pd.datetime.strftime(pd.datetime.now(), format='%Y%m%d')
        perfs['Name']              = db_id
        perfs['Sharpe.Ratio']      = tsu.get_sharpe_ratio(metrics['Returns'].values, risk_free = riskfree)
        perfs['Returns']           = (((metrics['Returns'] + 1).cumprod()) - 1)[-1]
        perfs['Max.Drawdown']      = max(metrics['Max.Drawdown'])
        perfs['Volatility']        = np.mean(metrics['Volatility'])
        perfs['Beta']              = np.mean(metrics['Beta'])
        perfs['Alpha']             = np.mean(metrics['Alpha'])
        perfs['Benchmark.Returns'] = (((metrics['Benchmark.Returns'] + 1).cumprod()) - 1)[-1]

        if save:
            self.feeds.stock_db.save_performances(perfs)
        return perfs

    #TODO Save returns
    def get_returns(self, benchmark=None, timestamp='one_month', save=False, db_id=None):
        returns = dict()

        if benchmark:
            #TODO Benchmark fields in database for guessing name like for stocks
            benchmark_symbol = '^GSPC'
            benchmark_data  = get_benchmark_returns(benchmark_symbol, self.backtest_cfg['start'], self.backtest_cfg['end'])
        else:
            raise NotImplementedError()
        #benchmark_data = [d for d in benchmark_data if (d.date >= self.backtest_cfg['start']) and (d.date <= self.backtest_cfg['end'])]
        dates = pd.DatetimeIndex([d.date for d in benchmark_data])

        returns['Benchmark.Returns']  = pd.Series([d.returns for d in benchmark_data], index=dates)
        returns['Benchmark.CReturns'] = ((returns['Benchmark.Returns'] + 1).cumprod()) - 1
        returns['Returns']            = pd.Series(self.results.returns, index=dates)
        returns['CReturns']           = pd.Series(((self.results.returns + 1).cumprod()) - 1, index=dates)

        df = pd.DataFrame(returns, index=dates)

        if save:
            raise NotImplementedError()
            self.feeds.stock_db.saveDFToDB(df, table=db_id)

        if benchmark is None:
            df = df.drop(['Benchmark.Returns', 'Benchmark.CReturns'], axis=1)
        return df

    def _get_index(self, perfs):
        #NOTE No frequency infos or just period number ?
        start = pytz.utc.localize(pd.datetime.strptime(perfs[0]['period_label'] + '-01', '%Y-%m-%d'))
        end = pytz.utc.localize(pd.datetime.strptime(perfs[-1]['period_label'] + '-01', '%Y-%m-%d'))
        return pd.date_range(start - pd.datetools.BDay(10), end, freq=pd.datetools.MonthBegin())

    def _extract_perf(self, perfs, field):
        index = self._get_index(perfs)
        values = [perfs[i][field] for i in range(len(perfs))]
        return pd.Series(values, index=index)

    def __del__(self):
        del self.server
Exemplo n.º 4
0
def genetic_optimize(domain,
                     cost_obj,
                     popsize=50,
                     step=1,
                     mutprob=0.2,
                     elite=0.2,
                     maxiter=100,
                     stop=0,
                     notify_android=False):
    '''
    Parameter optimization using genetic algorithm
    ______________________________________________
    Parameters
        domain: vec(...) of tuple(2)
            define range for each parameter
        cost_obj: Metric(1)
            compute the score of a solution
        popsize: int(1)
            number of solution set in one generation
        step: float(1)
            sensibility used while mutating a parameter
        mutprob: float(1)
            probability for a solution to mutate
        elite: float(1)
            % of best chromosomes selected
        maxiter: int(1)
            maximum number of population evolution
        stop: float(1)
            stop the algorithm when the fitness function reachs this value
        notify_android: bool(1)
            flag that let you send a notificatioin on android device when the algo is done
    ______________________________________________
    Return
        scores[0][0]: float(1)
            Best score the algorithm reached
        scores[0][1]: vec(...) of float(1)
           parameters that gave the best score
    '''
    # Initialisation
    client = ZMQ_Dealer(id=genetic_optimize.__name__)
    client.run(host='127.0.0.1', port=5570)
    check_buffer = [1] * 4

    # Mutation Operation
    def mutate(vec):
        i = random.randint(0, len(domain) - 1)
        if random.random() < 0.5 and vec[i] > domain[i][0]:
            mutated_param = vec[i] - step if vec[i] - step >= domain[i][
                0] else domain[i][0]
        elif vec[i] < domain[i][1]:
            mutated_param = vec[i] + step if vec[i] + step <= domain[i][
                0] else domain[i][0]
        return vec[0:i] + [mutated_param] + vec[i + 1:]

    # Crossover Operation
    def crossover(r1, r2):
        i = random.randint(1, len(domain) - 2)
        return r1[0:i] + r2[i:]

    def should_stop(best):
        ''' Break the loop if no longer evolution, or reached stop criteria '''
        check_buffer.append(best)
        check_buffer.pop(0)
        return (best >= check_buffer[0]) or (best <= stop)

    log.info('Build the initial population')
    pop = []
    for i in range(popsize):
        vec = [
            random.randint(domain[i][0], domain[i][1])
            for i in range(len(domain))
        ]
        pop.append(vec)

    # How many winners from each generation?
    topelite = int(elite * popsize)

    log.info('Run main loop')
    for i in range(maxiter):
        log.info('Rank population')
        scores = [(cost_obj.fitness(v), v) for v in pop]
        scores.sort()
        ranked = [v for (s, v) in scores]

        # Start with the pure winners
        log.info('Select elite')
        pop = ranked[0:topelite]

        # Add mutated and bred forms of the winners
        log.info('Evolve loosers')
        while len(pop) < popsize:
            if random.random() < mutprob:
                log.debug('Process mutation')
                # Mutation
                c = random.randint(0, topelite)
                pop.append(mutate(ranked[c]))
            else:
                # Crossover
                log.debug('Process crossover')
                c1 = random.randint(0, topelite)
                c2 = random.randint(0, topelite)
                pop.append(crossover(ranked[c1], ranked[c2]))

        #TODO add worst
        log.error(scores)
        log.notice('Best score so far: {}'.format(scores[0][0]))
        client.send(
            {
                'best': scores[0],
                'worst': scores[-1],
                'parameters': scores[0][1],
                'mean': np.mean([s[0] for s in scores]),
                'std': np.std([s[0] for s in scores]),
                'iteration': i + 1,
                'progress': round(float(i + 1) / float(maxiter), 2) * 100.0
            },
            type='optimization',
            channel='dashboard')
        if should_stop(scores[0][0]):
            log.info('Stop criteria reached, done with optimization.')
            break

    if notify_android:
        client.send_to_android({
            'title':
            'Optimization done',
            'priority':
            1,
            'description':
            'Genetic algorithm evolved the solution to {} \
                                               (with parameters {})'.format(
                scores[0][0], scores[0][1])
        })
    return scores[0][0], scores[0][1]