Пример #1
0
    def generate_theoretical_data(self,
                                  ticker_tgt,
                                  ticker_src,
                                  step=0.00005,
                                  pos_adj=None,
                                  neg_adj=None):
        """Generates theoretical data for a stock based on another
        stock.

        Given two tickers, a granularity/precision step, and manual
        offset/adjustments, generates more data for the first stock
        (gen) to match the length of data in the second stock (src).
        The generation is based on averages in existing real data and
        assumes an existing correlation between two stocks (e.g. UPRO
        and SPY supposedly have a correlation, or leverage factor of 3)

        Args:
            ticker_tgt: A ticker of the stock for which data should be
                generated, i.e. the target for the generation
            ticker_src: A ticker of the stock to be used as the data
                source to aid in data generation.
                NOTE: This implies the source data should be longer
                than the data for the stock for which the generation
                occurs
            step: A value corresponding to a level of precision, or the
                number of averages calculated and then used to generate
                the data. NOTE: precision != accuracy and a default
                value of 0.00005 is used if one is not given, based on
                testing done on different values
            pos_adj: A value to be used when adjusting movements in the
                positive direction, i.e. a higher value will lead to
                more pronounced positive moves (default: None, if None
                a hardcoded default value will be used depending on
                the ticker, typically 0)
            neg_adj: A value to be used when adjusting movements in the
                negative direction, i.e. a higher value will lead to
                more pronounced negative moves (default: None, if None
                a hardcoded default value will be used depending on
                the ticker, typically 0)

        Returns:
            A tuple of price LUTs, one LUT containing real data
            appended to a part of the generated data, the other
            containing a full set of generated data. The former is
            intended to be used in backtesting strategies, while the
            latter is intended to be used for verifying generation
            accuracy against existing real data.
        """
        db = DataManager()
        # get prices for tickers
        price_lut_tgt = db.build_price_lut(ticker_tgt)
        price_lut_src = db.build_price_lut(ticker_src)
        # before doing any calculations, check if all data is on disk already
        # NOTE: feature disabled for now, as it didnt respond to changes
        # price_lut_gen_part = db.build_price_lut(ticker_tgt + '--GEN-PART')
        # price_lut_gen_full = db.build_price_lut(ticker_tgt + '--GEN-FULL')
        # if (len(price_lut_gen_part) == len(price_lut_src)
        #         and len(price_lut_gen_full) == len(price_lut_src)):
        #     return (price_lut_gen_part, price_lut_gen_full)
        # sorted dates needed later
        src_dates = sorted(price_lut_src.keys())
        gen_dates = sorted(price_lut_tgt.keys())
        # part of data will be real data
        price_lut_gen_part = price_lut_tgt.copy()
        # fully generated data needs a real point as an anchor
        price_lut_gen_full = {gen_dates[0]: price_lut_tgt[gen_dates[0]]}
        # a set of adjustments to use if not otherwise specified
        adjustments = {
            'UPRO': (0, 0),
            'TMF': (0.01, 0.05),
            'TQQQ': (0.025, 0),
            'UDOW': (0, 0.01)
        }
        if step == 0.00005 and pos_adj is None and neg_adj is None:
            try:
                pos_adj = adjustments[ticker_tgt.upper()][0]
                neg_adj = adjustments[ticker_tgt.upper()][1]
            except KeyError:
                pos_adj = 0
                neg_adj = 0
        # calculate % movements and leverage ratio, to use for the SA-LUT
        moves = {}
        ratios = {}
        for i in range(len(gen_dates) - 1):
            change_src = (
                price_lut_src[gen_dates[i + 1]] / price_lut_src[gen_dates[i]] -
                1)
            change_gen = (
                price_lut_tgt[gen_dates[i + 1]] / price_lut_tgt[gen_dates[i]] -
                1)
            moves[gen_dates[i + 1]] = change_src
            if change_src == 0:
                ratios[gen_dates[i + 1]] = 0.0
            else:
                ratios[gen_dates[i + 1]] = change_gen / change_src
        sa_lut = SteppedAvgLookup(step, [moves[d] for d in gen_dates[1:]],
                                  [ratios[d] for d in gen_dates[1:]])
        # generate data going forward from gen data's anchor point
        for i in range(len(gen_dates) - 1):
            move = moves[gen_dates[i + 1]]
            if move >= 0:
                adj = pos_adj
            else:
                adj = neg_adj
            price_lut_gen_full[gen_dates[i + 1]] = \
                (price_lut_gen_full[gen_dates[i]]
                 * (move * (sa_lut.get(move) + adj) + 1))
        # generate data going backwards from gen data's anchor point
        for i in range(len(src_dates) - len(gen_dates) - 1, -1, -1):
            move = (
                price_lut_src[src_dates[i + 1]] / price_lut_src[src_dates[i]] -
                1)
            if move >= 0:
                adj = pos_adj
            else:
                adj = neg_adj
            gen_price = (price_lut_gen_full[src_dates[i + 1]] /
                         (move * (sa_lut.get(move) + adj) + 1))
            price_lut_gen_full[src_dates[i]] = gen_price
            price_lut_gen_part[src_dates[i]] = gen_price
        # save data to disk for faster retrieval next time
        db.write_stock_data(
            ticker_tgt + '--GEN-FULL',
            [[date, '-', '-', '-',
              str(price_lut_gen_full[date]), '-']
             for date in src_dates], False)
        db.write_stock_data(
            ticker_tgt + '--GEN-PART',
            [[date, '-', '-', '-',
              str(price_lut_gen_part[date]), '-']
             for date in src_dates], False)
        return (price_lut_gen_part, price_lut_gen_full)
Пример #2
0
class Market(object):

    """A Market containing stocks and a date.

    Can be queried for stock prices at the current date of this Market

    Attributes:
        stocks: A map of stock tickers to price LUTs
        new_period: A map of flags for market periods
        dates: An array of dates for the market
        date: A tuple containing (curr date index in dates, curr date)

    Todo:
    """

    def __init__(self, tickers=None, dates=None):
        """Intialize a Market with a set of dates and stock tickers
        with corresponding price LUTs.

        Args:
            tickers: An array of tickers for which to build price LUTs
            dates: An array of dates
        """
        self._db = DataManager()
        self.new_period = {'m': False, 'q': False, 'y': False}
        self.commissions = 10
        self.stocks = {}
        self.stocks_indicators = {}
        if tickers != None:
            self.add_stocks(tickers)
        self.dates = []
        self.date = (-1, None)
        if dates != None:
            self.dates = dates
            self.date = (0, self.dates[0])

    def add_stocks(self, tickers):
        """Creates price LUTs and adds them to the Market. Also sets up
        the data structures for indicators.

        Args:
            tickers: An array of tickers for which to create LUTs
        """
        for ticker in tickers:
            self.stocks[ticker.upper()] \
                = self._db.build_price_lut(ticker.upper())
            # create empty dict to be populated later by indicators
            self.stocks_indicators[ticker.upper()] = {}

    def add_indicator(self, ticker, indicator, indicator_lut):
        """Adds the indicator data for a ticker to this Market.

        Args:
            ticker: A ticker for which to add indicator data
            indicator: A string for the indicator being added
            indicator_lut: A lookup table for the indicator data itself
        """
        self.stocks_indicators[ticker.upper()][indicator.upper()] = \
            indicator_lut

    def inject_stock_data(self, ticker, dates, prices, price_lut=None):
        """Injects provided stock data into this market.

        Generally used for generated data, but can be used in any case
        to bypass the default price LUT creation method.

        Args:
            ticker: A ticker for which to inject data
            dates: An array of dates corresponding to the prices
            prices: An array of prices corresponding to the dates
            price_lut: A price lookup table with dates mapping to
                prices to be used instead of building one from dates
                and prices
        """
        ticker = ticker.upper()
        self.stocks_indicators[ticker] = {}
        if price_lut:
            self.stocks[ticker] = price_lut
            return
        price_lut = {}
        for i in range(0, len(dates)):
            price_lut[dates[i]] = prices[i]
        self.stocks[ticker] = price_lut

    def current_date(self):
        """Returns the current date of this Market.

        Returns:
            A string representing the current date in this Market
        """
        return date_str(self.date[1])

    def query_stock(self, ticker, num_days=0):
        """Query a stock at the current date.

        Args:
            ticker: A ticker to query
            num_days: A value representing the number of days of prices
                going backwards from the current date to return. A
                value of 0 means only a float for today's value will be
                returned. A value >0 means an array of that many values
                will be returned (default: 0)

        Returns:
            A float representing the price of the stock
        """
        ticker = ticker.upper()
        if num_days:
            dates = self.dates[
                max(0, self.date[0] - num_days + 1):self.date[0] + 1]
            return [float(self.stocks[ticker][date]) for date in dates]
        try:
            return float(self.stocks[ticker][self.current_date()])
        except KeyError:
            print("NEEDS FIX: no data for " + ticker + " at " + self.date[1])
            return None

    def query_stock_indicator(self, ticker, indicator):
        """Query a stock indicator value or set of values at the
        current date.

        Args:
            ticker: A ticker to query
            indicator: An identifying string for the indicator value to
                return

        Returns:
            A float or set of floats representing the indicator
            value(s)
        """
        ticker = ticker.upper()
        indicator = indicator.upper()
        try:
            return float(
                self.stocks_indicators[ticker][indicator][self.current_date()])
        except KeyError:
            print('NEEDS FIX: no {} value for {} at {}'.format(
                indicator, ticker, self.current_date()))
            return None

    def set_date(self, date):
        """Sets this Market to a given date.

        Args:
            date: A date to which to set this Market
        """
        if date < self.dates[0]:
            self.date = (0, self.dates[0])
            return 0
        if date > self.dates[-1]:
            self.date = (len(self.dates) - 1, self.dates[-1])
            return 0
        try:
            self.date = (self.dates.index(date), date)
            return 0
        except ValueError:
            print("NEEDS FIX: date does not exist")
            return 1

    def set_default_dates(self):
        """Sets a default range for this Market's dates.

        Based on existing stocks in this Market, decides an appropriate
        range in which all stocks have prices.
        """
        date_range = (date_str(dt.fromordinal(1)),
                      date_str(dt.fromordinal(999999)))
        for price_lut in self.stocks.values():
            dates = sorted(price_lut.keys())
            date_range = (max(date_range[0], dates[0]), min(
                date_range[1], dates[-1]))
            date_idxs = (dates.index(
                date_range[0]), dates.index(date_range[1]))
            self.dates = dates[date_idxs[0]:date_idxs[1] + 1]
        self.date = (0, self.dates[0])

    def advance_day(self):
        """Advances this Market's date by one day."""
        self.date = (self.date[0] + 1, self.dates[self.date[0] + 1])
        self._raise_period_flags()

    def _raise_period_flags(self):
        """Internal function to handle setting flags at new periods."""
        last_date = date_obj(self.dates[self.date[0] - 1])
        curr_date = date_obj(self.date[1])
        self.new_period = {'m': False, 'q': False, 'y': False}
        if last_date.year < curr_date.year:
            self.new_period = {'m': True, 'q': True, 'y': True}
        elif last_date.month != curr_date.month:
            self.new_period['m'] = True
            if (curr_date.month - 1) % 3 == 0:
                self.new_period['q'] = True