Ejemplo n.º 1
0
    def run(self, data, start, end, symbols, source, lookback=0):
        data_start = self.get_data_start(start, lookback)

        # Current caching implementation based on Zipline
        symbols_data = dict()
        for symbol in symbols:
            symbol_path = self.sanitize_name(symbol)
            cache_filename = "{stock}-{start}-{end}.csv".format(
                stock=symbol_path, start=data_start,
                end=end).replace(':', '-')
            cache_filepath = self.get_cache_filepath(cache_filename)
            if os.path.exists(cache_filepath):
                symbol_data = pd.DataFrame.from_csv(cache_filepath)
            else:
                symbol_data = web.DataReader(symbol, source, data_start,
                                             end).sort_index()
                symbol_data.to_csv(cache_filepath)
            symbols_data[symbol] = symbol_data

        symbols_panel = pd.concat(symbols_data).to_panel()
        symbols_panel = symbols_panel.swapaxes('minor', 'major')
        if symbols_panel.empty:
            ProphetException("No data for the range specified:"
                             " %s to %s" % (data_start, end))

        symbols_panel = symbols_panel.fillna(method='ffill')
        symbols_panel = symbols_panel.fillna(method='bfill')
        symbols_panel = symbols_panel.fillna(1.0)
        return symbols_panel.loc[:, ((symbols_panel.major_axis >= data_start)
                                     & (symbols_panel.major_axis <= end))]
Ejemplo n.º 2
0
def backtest(cash,
             data,
             start,
             end,
             order_generator,
             slippage=0.0,
             commission=0,
             portfolio=Portfolio()):
    """ Backtesting function for Prophet
    """
    portfolio_shares = defaultdict(lambda: 0)
    portfolio_values = []
    prices = data.get('prices')
    if prices is None:
        raise ProphetException("Price data is required to run a backtest. "
                               "Please add a data generator with the name "
                               "property set to 'price'.")

    timestamps = prices.index.to_series().loc[start:]
    # the orders object will hold orders for a particular timestamp
    # ordersDict, on the other hand, holds every order over the life
    # of the backtest with a timestamp key: type <class 'pandas._libs.tslib.Timestamp'>
    ordersDict = {}

    for timestamp in timestamps:
        orders = order_generator.run(data=data,
                                     timestamp=timestamp,
                                     prices=prices,
                                     cash=cash,
                                     portfolio=portfolio)

        if orders is not None:
            if len(orders) > 0:
                ordersDict[timestamp] = orders

            for order in orders:
                # Get the price after slippage
                price = prices[order.symbol].loc[timestamp]
                if order.shares < 0:
                    adjusted_price = price * (1 - slippage)
                else:
                    adjusted_price = price * (1 + slippage)

                cash -= order.shares * adjusted_price
                cash -= commission
                portfolio_shares[order.symbol] += order.shares

        # Calculate total portfolio value for current timestamp
        current_value = cash
        portfolio_value = [
            prices[symbol].loc[timestamp] * shares
            for symbol, shares in iteritems(portfolio_shares)
        ]
        current_value += sum(portfolio_value)
        portfolio_values.append(current_value)

    return BackTest(dict(zip(timestamps, portfolio_values)),
                    timestamps,
                    ordersDict=ordersDict,
                    prices=prices.loc[start:])
Ejemplo n.º 3
0
    def generate_orders(self,
                        target_datetime,
                        lookback=0,
                        cash=1000000,
                        buffer_days=0,
                        portfolio=Portfolio()):
        """ Generates orders for a given day. Useful for generating trade
        orders for a your personal account.

        Args:
            target_datetime (datetime): The datetime you want to
                generate orders for.
            lookback (int): Number of trading days you want data for before the
                (target_datetime - buffer_days)
            cash (int): Amount of starting cash
            buffer_days (int): number of trading days you want extra data
                generated for. Acts as a data start date.
            portfolio (prophet.portfolio.Portfolio): Starting portfolio
        """
        target_datetime_index = trading_days.get_loc(target_datetime)
        start = trading_days[target_datetime_index - buffer_days]
        data = self._generate_data(start, target_datetime, lookback)
        prices = data.get('prices')
        if prices is None:
            raise ProphetException("Price data is required to run a backtest. "
                                   "Please add a data generator with the name "
                                   "property set to 'prices'.")
        return self._order_generator.run(prices=prices,
                                         data=data,
                                         timestamp=target_datetime,
                                         cash=cash,
                                         portfolio=portfolio)
Ejemplo n.º 4
0
    def run_backtest(
            self,
            start,
            end=None,
            lookback=0,
            slippage=0.0,
            commission=0.0,
            cash=1000000,  # $1,000,000
            initial_portfolio=Portfolio(),
    ):
        """ Runs a backtest over a given time period.

        Args:
            start (datetime): The start of the backtest window
            end (datetime): The end of the backtest windows
            lookback (int): Number of trading days you want data for
                before the start date
            slippage (float): Percent price slippage when executing order
            commission (float): Amount of commission paid per order
            cash (int): Amount of starting cash
            portfolio (prophet.portfolio.Portfolio): Starting portfolio

        Return:
            prophet.backtest.BackTest
        """
        # Setup
        if not end:
            today = dt.date.today()
            end = dt.datetime.combine(today, dt.time())

        if not self._order_generator:
            raise ProphetException("Must set an order generator by calling"
                                   "set_order_generator.")

        start_utc = pd.to_datetime(start, utc=True)
        end_utc = pd.to_datetime(end, utc=True)
        timestamps = trading_days[(trading_days >= start_utc)
                                  & (trading_days <= end_utc)]
        effective_start = timestamps[0]

        data = self._generate_data(start=effective_start,
                                   end=end,
                                   lookback=lookback)

        # Run backtest
        return backtest(
            cash=cash,
            data=data,
            start=effective_start,
            end=end,
            slippage=slippage,
            commission=commission,
            portfolio=initial_portfolio,
            order_generator=self._order_generator,
        )
def backtest(cash,
             data,
             start,
             end,
             order_generator,
             slippage=0.0,
             commission=0,
             portfolio=Portfolio()):
    """ Backtesting function for Prophet
    """
    portfolio_shares = defaultdict(lambda: 0)
    portfolio_values = []
    prices = data.get('prices')
    if prices is None:
        raise ProphetException("Price data is required to run a backtest. "
                               "Please add a data generator with the name "
                               "property set to 'price'.")

    timestamps = prices.index.to_series().loc[start:]

    # Check if the last day doesn't have data yet
    # Like if it was early in the morning
    if not timestamps[-1] in prices:
        timestamps = timestamps[:-1]

    for timestamp in timestamps:
        orders = order_generator.run(data=data,
                                     timestamp=timestamp,
                                     prices=prices,
                                     cash=cash,
                                     portfolio=portfolio)

        for order in orders:
            # Get the price after slippage
            price = prices[order.symbol].loc[timestamp]
            if order.shares < 0:
                adjusted_price = price * (1 - slippage)
            else:
                adjusted_price = price * (1 + slippage)

            cash -= order.shares * adjusted_price
            cash -= commission
            portfolio_shares[order.symbol] += order.shares

        # Calculate total portfolio value for current timestamp
        current_value = cash
        portfolio_value = [
            prices[symbol].loc[timestamp] * shares
            for symbol, shares in iteritems(portfolio_shares)
        ]
        current_value += sum(portfolio_value)
        portfolio_values.append(current_value)

    return BackTest(dict(zip(timestamps, portfolio_values)), index=timestamps)