コード例 #1
0
class PortfolioManager:
    '''
    Observes the trader universe and produces
    orders to be excuted within zipline
    Abstract method meant to be used by user
    to construct their portfolio optimizer
    '''
    __metaclass__ = abc.ABCMeta

    #TODO Add in the constructor or setup parameters some general settings like maximum weights, positions, frequency,...
    def __init__(self, parameters):
        '''
        Parameters
            parameters : dict(...)
                Named parameters used either for general portfolio settings
                (server and constraints), and for user optimizer function
        '''
        super(PortfolioManager, self).__init__()
        self.log       = Logger('Manager')
        self.datafeed      = DataFeed()
        self.portfolio = None
        self.date      = None
        self.name      = parameters.get('name', 'Chuck Norris')
        self._optimizer_parameters = parameters
        self.connected = False
        #TODO Should send stuff anyway, and accept new connections while running

        self.connected = parameters.get('connected', False)

        # Run the server if the engine didn't while it is asked
        if 'server' in parameters:
            self.server = parameters.pop('server')
            if self.server.port is None and self.connected:
                self.log.info('Binding manager on default port...')
                self.server.run(host='127.0.0.1', port=5570)

    @abc.abstractmethod
    def optimize(self):
        '''
        Users must overwrite this method
        '''
        pass

    def update(self, portfolio, date):
        '''
        Actualizes the portfolio universe
        and if connected, sends it through the wires
        ________________________________
        Parameters
            portfolio: zipline.portfolio(1)
                ndict object storing portfolio values at the given date
            date: datetime.datetime(1)
                Current date in zipline simulation
        '''
        self.portfolio = portfolio
        self.date      = date

        if self.connected:
            self.server.send(to_dict(portfolio),
                              type    = 'portfolio',
                              channel = 'dashboard')

            return self.catch_messages()
        return dict()

    def trade_signals_handler(self, signals):
        '''
        Process buy and sell signals from backtester or live trader
        @param signals: dict holding stocks of interest, format like {"google": 567.89, "apple": -345.98}
                       If the value is negative -> sell signal, otherwize buy one
        @return: dict orderBook, like {"google": 34, "apple": -56}
        '''
        orderBook       = dict()

        # If value < 0, it's a sell signal on the key, else buy signal
        to_buy          = [t for t in signals if signals[t] > 0]
        to_sell         = set(self.portfolio.positions.keys()).intersection([t for t in signals if signals[t] < 0])
        if not to_buy and not to_sell:
            # Nothing to do
            return dict()

        # Compute the optimal portfolio allocation, using user defined function
        alloc, e_ret, e_risk = self.optimize(self.date, to_buy, to_sell, self._optimizer_parameters)

        #TODO Check about selling in available money and handle 250 stocks limit
        #TODO Handle max_* as well, ! already actif stocks

        ## Building orders for zipline
        #NOTE The follonwing in a separate function that could be used when catching message from user
        for t in alloc:
            ## Handle allocation returned as number of stocks to order
            if isinstance(alloc[t], int):
                orderBook[t] = alloc[t]

            ## Handle allocation returned as stock weights to order
            elif isinstance(alloc[t], float):
                # Sell orders
                if alloc[t] <= 0:
                    orderBook[t] = int(alloc[t] * self.portfolio.positions[t].amount)
                ## Buy orders
                else:
                    ## If we already trade this ticker, substract owned amount before computing number of stock to buy
                    if self.portfolio.positions[t].amount:
                        price = self.portfolio.positions[t].last_sale_price
                    else:
                        price = signals[t]
                    orderBook[t] = (int(alloc[t] * self.portfolio.portfolio_value / price)
                                    - self.portfolio.positions[t].amount)

        return orderBook

    def setup_strategie(self, parameters):
        '''
        General parameters or user ones setting
        (maw_weigth, max_assets, max_frequency, commission cost)
        ________________________________________________________
        Parameters
            parameters: dict(...)
                Arbitrary values to change general constraints,
                or for user algorithm settings
        '''
        for name, value in parameters.iteritems():
            self._optimizer_parameters[name] = value

    #TODO Still need here this dict = f(ndict)
    def save_portfolio(self, portfolio):
        '''
        Store in database given portfolio,
        for reuse later or further analysis puropose
        ____________________________________________
        Parameters
            portfolio: zipline.protocol.Portfolio(1)
                ndict portfolio object to store
        ___________________________________________
        '''
        self.log.info('Saving portfolio in database')
        self.datafeed.stock_db.save_portfolio(portfolio, self.name, self.date)

    def load_portfolio(self, name):
        '''
        Load a complete portfolio object from database
        ______________________________________________
        Parameters
            name: str(...)
                name used as primary key in db for the portfolio
        ______________________________________________
        Return
            The portfolio with the given name if found,
            None otherwize
        '''
        self.log.info('Loading portfolio from database')
        ## Get the portfolio as a pandas Serie
        db_pf = self.datafeed.saved_portfolios(name)
        ## The function returns None if it didn't find a portfolio with id 'name' in db
        if db_pf is None:
            return None

        # Creating portfolio object
        portfolio = zp.Portfolio()

        portfolio.capital_used = db_pf['Capital']
        portfolio.starting_cash = db_pf['StartingCash']
        portfolio.portfolio_value = db_pf['PortfolioValue']
        portfolio.pnl = db_pf['PNL']
        portfolio.returns = db_pf['Returns']
        portfolio.cash = db_pf['Cash']
        portfolio.start_date = db_pf['StartDate']
        portfolio.positions = self._adapt_positions_format(db_pf['Positions'])
        portfolio.positions_value = db_pf['PositionsValue']

        return portfolio

    def _adapt_positions_type(self, db_pos):
        '''
        From array of sql Positions data model
        To Zipline Positions object
        '''
        positions = zp.Positions()

        for pos in db_pos:
            if pos.Ticker not in positions:
                positions[pos.Ticker] = zp.Position(pos.Ticker)
            position = positions[pos.Ticker]
            position.amount = pos.Amount
            position.cost_basis = pos.CostBasis
            position.last_sale_price = pos.LastSalePrice

        return positions

    def catch_messages(self, timeout=1):
        '''
        Listen for user messages,
        process usual orders
        '''
        msg = self.server.noblock_recv(timeout=timeout, json=True)
        #TODO msg is a command or an information, process it
        if msg:
            self.log.info('Got message from user: {}'.format(msg))
        return msg
コード例 #2
0
ファイル: portfolio.py プロジェクト: vzs/ppQuanTrade
class PortfolioManager(object):
    '''
    Manages portfolio during simulation, and stays aware of the situation
    through the update() method. It is configured through zmq message (manager
    field) or QuanTrade/config/managers.json file.

    User strategies call it with a dictionnnary of detected opportunities (i.e.
    buy or sell signals).  Then the optimize function computes assets
    allocation, returning a dictionnary of symbols with their weigths or amount
    to reallocate.
                                  __________________________      _____________
    signals {'google': 745.5} --> |                         | --> |            |
                                  | trade_signals_handler() |     | optimize() |
    orders  {'google': 34}    <-- |_________________________| <-- |____________|

    In addition, portfolio objects can be saved in database and reloaded later,
    and user on-the-fly orders are catched and executed in remote mode. Finally
    portfolios are connected to the server broker and, if requested, send state
    messages to client.

    This is abstract class, inheretid class will eventally overwrite optmize()
    to expose their own asset allocation strategy.
    '''

    __metaclass__ = abc.ABCMeta

    #TODO Add in the constructor or setup parameters some general settings like maximum weights, positions, frequency,...
    #TODO Better to return 0 stocks to trade: remove the field
    #NOTE Regarding portfolio constraints: from a set of user-defined
    #parameters, a unnique set should be constructed. Then the solution provided
    #by optimize function would have to be a subset of it. (classic mathematical solution)
    #Finally it should be defined how to handle non-correct solutions
    def __init__(self, configuration):
        '''
        Parameters
            configuration : dict
                Named parameters used either for general portfolio settings
                (server and constraints), and for user optimizer function
        '''
        super(PortfolioManager, self).__init__()

        # Easy mysql access
        self.datafeed  = DataFeed()

        # Zipline portfolio object, updated during simulation with self.date
        self.portfolio = None
        self.date      = None

        # Portfolio owner, mainly used for database saving and client communication
        self.name      = configuration.get('name', 'ChuckNorris')
        self.log       = logbook.Logger('Manager::' + self.name)

        # Other parameters are used in user optimize() method
        self._optimizer_parameters = configuration

        # Make the manager talk to connected clients
        self.connected = configuration.get('connected', False)
        # Send android notifications when orders are processed
        # It's only possible with a running server
        self.android   = configuration.get('android', False) & self.connected

        # Delete from database data with the same portfolio name
        if configuration.get('clean', True):
            self.log.info('Cleaning previous trades.')
            clean_previous_trades(self.name)

        # Run the server if the engine didn't, while it is asked for
        if 'server' in configuration and self.connected:
            # Getting server object instanciated anyway before (by Setup object)
            self.server = configuration.pop('server')

        # Web based dashboard where real time results are monitored
        #FIXME With dynamic generation, dashboad never exists at this point
        #self.dashboard = Dashboard(self.name)

        # In case user optimization would need to retrieve more data
        self.remote = Remote()

    @abc.abstractmethod
    def optimize(self):
        '''
        Users should overwrite this method
        '''
        pass

    def update(self, portfolio, date, metrics=None, save=False, widgets=False):
        '''
        Actualizes the portfolio universe
        and if connected, sends it through the wires
        ________________________________
        Parameters
            portfolio: zipline.portfolio
                ndict object storing portfolio values at the given date
            date: datetime.datetime
                Current date in zipline simulation
            save: boolean
                If true, save the portfolio in database under self.name key
        '''
        # Make the manager aware of current simulation portfolio and date
        self.portfolio = portfolio
        self.date      = date

        if save:
            self.save_portfolio(portfolio)
            if metrics is not None:
                save_metrics_snapshot(self.name, self.date, metrics)

        # Delete sold items and add new ones on dashboard
        #if widgets:
            #self.dashboard.update_position_widgets(self.portfolio.positions)


        # Send portfolio object to client
        if self.connected:
            #NOTE Something smarter ?
            # We need to translate zipline portfolio and position objects into json data (i.e. dict)
            packet_portfolio = to_dict(portfolio)
            for pos in packet_portfolio['positions']:
                packet_portfolio['positions'][pos] = to_dict(packet_portfolio['positions'][pos])

            self.server.send(packet_portfolio,
                             type   ='portfolio',
                             channel='dashboard')

            # Check user remote messages and return it
            return self.catch_messages()
        return dict()

    def trade_signals_handler(self, signals, extras={}):
        '''
        Process buy and sell signals from the simulation
        ___________________________________________________________
        Parameters
            signals: dict
                hold stocks of interest, format like {"google": 0.8, "apple": -0.2}
                If the value is negative -> sell signal, otherwize buy one
                Values are ranged between [-1 1] regarding signal confidence
            extras: whatever
                Object sent from algorithm for certain managers
        ___________________________________________________________
        Return
            dict orderBook, like {"google": 34, "apple": -56}
        '''

        self._optimizer_parameters['algo'] = extras
        orderBook = dict()

        # If value < 0, it's a sell signal on the key, else buy signal
        to_buy = dict(filter(lambda (sid, strength): strength > 0, signals.iteritems()))
        to_sell = dict(filter(lambda (sid, strength): strength < 0, signals.iteritems()))
        #to_buy    = [t for t in signals if signals[t] > 0]
        #NOTE With this line we can't go short
        #to_sell   = set(self.portfolio.positions.keys()).intersection(
                #[t for t in signals if signals[t] < 0])

        if not to_buy and not to_sell:
            # Nothing to do
            return dict()

        # Compute the optimal portfolio allocation, using user defined function
        alloc, e_ret, e_risk = self.optimize(self.date, to_buy, to_sell, self._optimizer_parameters)

        #TODO Check about selling in available money and handle 250 stocks limit
        #TODO Handle max_* as well, ! already actif stocks

        ## Building orders for zipline
        #NOTE The follonwing in a separate function that could be used when catching message from user
        for t in alloc:
            ## Handle allocation returned as number of stocks to order
            if isinstance(alloc[t], int):
                orderBook[t] = alloc[t]

            ## Handle allocation returned as stock weights to order
            elif isinstance(alloc[t], float):
                # Sell orders
                if alloc[t] <= 0:
                    orderBook[t] = int(alloc[t] * self.portfolio.positions[t].amount)
                ## Buy orders
                else:
                    ## If we already trade this ticker, substract owned amount before computing number of stock to buy
                    if self.portfolio.positions[t].amount:
                        price = self.portfolio.positions[t].last_sale_price
                    else:
                        price = signals[t]
                    orderBook[t] = (int(alloc[t] * self.portfolio.portfolio_value / price)
                                    - self.portfolio.positions[t].amount)

        if self.android and orderBook:
            # Alert user of the orders about to be processed
            # Ok... kind of fancy method
            ords = {'-1': 'sell', '1': 'buy'}
            msg = 'QuanTrade suggests you to '
            msg += ', '.join(['{} {} stocks of {}'
                .format(ords[str(amount / abs(amount))], amount, ticker) for
                ticker, amount in orderBook.iteritems()])
            self.server.send_to_android({'title': 'Portfolio manager notification',
                                         'priority': 1,
                                         'description': msg})

        return orderBook

    def setup_strategie(self, parameters):
        '''
        General parameters or user settings
        (maw_weigth, max_assets, max_frequency, commission cost)
        ________________________________________________________
        Parameters
            parameters: dict
                Arbitrary values to change general constraints,
                or for user algorithm settings
        '''
        assert isinstance(parameters, dict)
        for name, value in parameters.iteritems():
            self._optimizer_parameters[name] = value

    def save_portfolio(self, portfolio):
        '''
        Store in database given portfolio,
        for reuse later or further analysis puropose
        ____________________________________________
        Parameters
            portfolio: zipline.protocol.Portfolio(1)
                ndict portfolio object to store
        ___________________________________________
        '''
        self.log.debug('Saving portfolio in database')
        self.datafeed.stock_db.save_portfolio(portfolio, self.name, self.date)

    def load_portfolio(self, name):
        '''
        Load a complete portfolio object from database
        ______________________________________________
        Parameters
            name: str(...)
                name used as primary key in db for the portfolio
        ______________________________________________
        Return
            The portfolio with the given name if found,
            None otherwize
        '''
        self.log.info('Loading portfolio {} from database'.foramt(name))
        # Get the portfolio as a pandas Serie
        db_pf = self.datafeed.saved_portfolios(name)

        # Create empty Portfolio object to be filled
        portfolio = zp.Portfolio()

        # The function returns an empty dataframe if it didn't find a portfolio with id 'name' in db
        if len(db_pf):
            # Fill new portfolio data structure
            portfolio.capital_used    = db_pf['Capital']
            portfolio.starting_cash   = db_pf['StartingCash']
            portfolio.portfolio_value = db_pf['PortfolioValue']
            portfolio.pnl             = db_pf['PNL']
            portfolio.returns         = db_pf['Returns']
            portfolio.cash            = db_pf['Cash']
            portfolio.start_date      = db_pf['StartDate']
            portfolio.positions       = self._adapt_positions_type(db_pf['Positions'])
            portfolio.positions_value = db_pf['PositionsValue']

        return portfolio

    def _adapt_positions_type(self, db_pos):
        '''
        From array of sql Positions data model
        To Zipline Positions object
        '''
        # Create empty Positions object to be filled
        positions = zp.Positions()

        for pos in db_pos:
            if pos.Ticker not in positions:
                positions[pos.Ticker] = zp.Position(pos.Ticker)
            position = positions[pos.Ticker]
            position.amount = pos.Amount
            position.cost_basis = pos.CostBasis
            position.last_sale_price = pos.LastSalePrice

        return positions

    def catch_messages(self, timeout=1):
        '''
        Listen for user messages,
        process usual orders
        '''
        msg = self.server.noblock_recv(timeout=timeout, json=True)
        #TODO msg is a command or an information, process it
        if msg:
            self.log.info('Got message from user: {}'.format(msg))
            try:
                msg = json.loads(msg)
            except:
                msg = ''
                self.log.error('Unable to parse user message')
        return msg