示例#1
0
def TestCaseWithFit():
    from AprFit import AprFit
    from winsound import Beep
    
    # Pull the stock data into memory.
    stocks = []
    for symbol in symbols:
        stock = Stock.ShyRetrieve(symbol=symbol, minDate=(datetime.datetime.now() - datetime.timedelta(days=5)))
        if len(stock._history) > 0:
            stocks.append(stock)
        else:
            print(f"{stock.symbol} no history")
    
    Beep(300, 500) # Let the user know it's done importing

    today = datetime.datetime.now()

    # In a loop so data stays in memory if the user wants to change parameters.
    while True:
        yearsToConsider = AskYears()
        if yearsToConsider == "break":
            break
        scores = []

        startDate = today - datetime.timedelta(seconds = 366 * yearsToConsider * 60 * 60 * 24)

        for stock in stocks:
            if stock.history[0].date > startDate:
                continue
                # Only plot stocks which have enough history.

            try:
                apr_fit = stock.get_apr_fit(years=yearsToConsider)
            except Exception as ex:
                # Looks like I need to do some sanitizing of the data from yFinance. BEP hits a math domain error here and previously gave strange results in other tests.
                print(f"Failed to fit curve onto data from {stock.symbol}: {ex}")
                continue

            # Determine how much this stock is currently overvalued/undervalued
            total_dividends_since_t_0 = 0.
            index = len(stock.history) - 1
            while (stock.history[index].date - today).total_seconds() >= apr_fit.t_0 * 31557600.:
                total_dividends_since_t_0 += stock.history[index].dividend
                if index == 0:
                    break
                index -= 1
            undervalue = apr_fit.y_0 * (1 + apr_fit.rate) ** (0. - apr_fit.t_0) - (stock.history[-1].price + total_dividends_since_t_0)
            undervalue /= stock.history[-1].price + total_dividends_since_t_0

            N_STDEVS = 2. # Number of standard deviations to use for fitness
            if apr_fit.stdev * N_STDEVS > 1.:
                score = Score(stock, 0.)
            else:
                score = Score(stock, (apr_fit.rate + undervalue / 4.) * (1. - N_STDEVS * apr_fit.stdev) + 1. / stock.pe_ratio)
                # stdev applies as both uncertainty in the fit and as potential loss

            if math.isnan(score.score):
                print(f"Failed to calculate score for {stock.symbol}")
                continue

            # Boost the investments we want to bias towards
            if stock.symbol in boost:
                score.score *= 1.5

            # Nerf the stocks we don't like
            if stock.symbol in nerf:
                score.score *= 0.5

            # Attach metadata to the score so we can print it.
            score.rate        = 100. * apr_fit.rate
            score.undervalue  = 100. * undervalue
            score.uncertainty = 100. * apr_fit.stdev
            scores.append(score)

        scores.sort(key=lambda x:x.score)
        scores.reverse()

        # Recommend how much would have been good to allocated to each
        number_of_recommendations = 0
        sum = 0
        for candidate in scores:
            if math.isnan(candidate.score):
                continue
            if candidate.score <= 0.:
                continue
            sum += candidate.score
            if candidate.score / sum < 0.05:
                sum -= candidate.score
                break
            number_of_recommendations += 1

        # Don't recommend >25% in any one asset
        LIMIT = 0.25
        if scores[0].score > sum * LIMIT:
            redistribution = (scores[0].score - sum * LIMIT) / (number_of_recommendations - 1)
            for score in scores:
                score.score += redistribution
            if scores[1].score > sum * LIMIT:
                redistribution = (scores[1].score - sum * LIMIT) / (number_of_recommendations - 2)
                for score in scores:
                    score.score += redistribution
                scores[1].score = sum * LIMIT
            scores[0].score = sum * LIMIT

        print("Recommended distribution:")
        for i in range(number_of_recommendations):
            stock = scores[i].stock
            recommended_percent = 100 * scores[i].score / sum
            print(f"{recommended_percent:.2f}% in {stock.symbol}    Score: {scores[i].score:.2f}    P/E: {stock.pe_ratio:.2f}    <APR>: {scores[i].rate:.2f}    undervalued by {scores[i].undervalue:.2f}%    uncertainty: {scores[i].uncertainty:.2f}%")

            if plot:
                stock.get_apr_fit(years=yearsToConsider, plot=True)

        Beep(300, 500)
示例#2
0
def OriginalTestCase():

    # Import the modules now so it will crash before doing work if a module is missing.
    import matplotlib.pyplot as plt
    from dateutil.parser import parse

    # Pull the stock data into memory.
    stocks = []
    for symbol in symbols:
        stock = Stock.ShyRetrieve(symbol=symbol, minDate=(datetime.datetime.now() - datetime.timedelta(days=5)))
        if len(stock._history) > 0:
            stocks.append(stock)

    today = datetime.datetime.now()

    # Let the user know import is complete
    import winsound
    winsound.Beep(600, 100)

    # In a loop so data stays in memory if the user wants to change parameters.
    while True:
        yearsToConsider = AskYears()
        minimumYield = AskMinYield()
        if yearsToConsider == "break":
            break
        if minimumYield == "break":
            break

        startDate = today - datetime.timedelta(days = 365 * yearsToConsider)

        if plot:
            print("Plotting...")
            fig, subplots = plt.subplots(nrows=2, ncols=1)
            dividend_plot = subplots[0]
            #growth_plot = subplots[1]
            total_yield_plot = subplots[1]
            dividend_plot.set_ylabel("Dividend (%/yr)")
            dividend_plot.set_xlim(left=startDate, right=today)
            max_dividend = 0.
            min_dividend = 0.

            #growth_plot.set_ylabel("Growth Since (%/yr inflation adjusted)")
            #growth_plot.set_xlim(left=startDate, right=today)
            max_growth = 0.
            min_growth = 0.

            total_yield_plot.set_ylabel("Total Yield (%/yr)")
            total_yield_plot.set_xlim(left=startDate, right=today)

        max_total = 0.
        min_total = 0.

        annual_yields = []
        scores        = []

        for stock in stocks:
            if stock.history[0].date > startDate:
                continue
                # Only plot stocks which have enough history.

            filteredGrowth, growthUncertainty = stock.GrowthAPRWithUncertainty(yearsToConsider)
            dividendYield = stock.AverageDividendPercent(yearsToConsider)
            dividendUncertainty = stock.DividendPercentUncertainty(yearsToConsider)
            if dividendUncertainty > dividendYield:
                dividendUncertainty = dividendYield # Never apply a penalty for dividends
            annualYield = dividendYield + filteredGrowth
            if annualYield < minimumYield:
                continue
                # Only plot stocks with combined margin better than 10% per year over the past 10 years.
            print(f"{stock.symbol} {annualYield:.1f} ± {growthUncertainty + dividendUncertainty:.1f}% average annual yield over the past {yearsToConsider} years.")
            print(f"{filteredGrowth:.2f}% from growth and {stock.AverageDividendPercent(yearsToConsider):.2f}% from dividends.")
            annual_yields.append((stock.symbol, annualYield))

            score = annualYield - 0.431 * (growthUncertainty + dividendUncertainty) # 33rd percentile
            scores.append((stock.symbol, score))

            if plot:
                dates = []
                relGrowth = []
                divYieldPercent = []
                latest = stock.history[-1]
                for snapshot in stock.history:
                    if snapshot.date < startDate:
                        continue
                    if snapshot.price == 0.0:
                        continue
                    if (latest.date - snapshot.date).days == 0.0:
                        continue
                    annual_growth = 100. * (latest.price / snapshot.price - 1.) * 1.02 ** ((snapshot.date - latest.date).days / 365.25) / \
                        ((latest.date - snapshot.date).days / 365.25)
                    if annual_growth > 1. * (latest.date - snapshot.date).days:
                        annual_growth = 1. * (latest.date - snapshot.date).days
                    if annual_growth < -1. * (latest.date - snapshot.date).days:
                        annual_growth = -1. * (latest.date - snapshot.date).days
                    dates.append(snapshot.date)
                    relGrowth.append(annual_growth)
                    divYieldPercent.append(100. * snapshot.annualDividend / snapshot.price)
                    if relGrowth[-1] > max_growth:
                        max_growth = relGrowth[-1]
                    elif relGrowth[-1] < min_growth:
                        min_growth = relGrowth[-1]
                    if divYieldPercent[-1] > 100.:
                        print(f"{dates[-1]}: {stock.symbol} has {divYieldPercent[-1]}% dividend?")
                        print("You may not want to trust this data...")
                    if divYieldPercent[-1] > max_dividend:
                        max_dividend = divYieldPercent[-1]
                    elif divYieldPercent[-1] < min_dividend:
                        min_dividend = divYieldPercent[-1]

                dividend_plot.plot(dates, divYieldPercent, label=stock.symbol)
                #growth_plot.plot(dates, relGrowth, label=stock.symbol)

            annualYield = []
            yDates      = []
            for index in range(200, len(stock.history)):
                snapshot = stock.history[index]
                if snapshot.date < startDate:
                    continue
                priceLastYear = stock.history[index - 200].price
                if priceLastYear == 0.0:
                    continue
                yDates.append(snapshot.date)
                annualYield.append(100. * ((snapshot.price - priceLastYear) + snapshot.annualDividend) / priceLastYear)
                if annualYield[-1] > max_total:
                    max_total = annualYield[-1]
                elif annualYield[-1] < min_total:
                    min_total = annualYield[-1]
            if plot:
                total_yield_plot.plot(yDates, annualYield, label=stock.symbol)
                
        annual_yields.sort(key=lambda x:x[1])
        annual_yields.reverse()
        scores.sort(key=lambda x:x[1])
        scores.reverse()

        # Recommend how much would have been good to allocated to each
        number_of_recommendations = 0
        sum = 0
        for candidate in scores:
            if not math.isnan(candidate[1]):
                sum += candidate[1]
            if sum > 0.:
                if candidate[1] / sum < 0.05:
                    sum -= candidate[1]
                    break
            if sum < 0.:
                sum -= candidate[1]
                break
            number_of_recommendations += 1
        print("Recommended distribution:")
        recommendations = []
        for i in range(number_of_recommendations):
            symbol = scores[i][0]
            recommended_percent = 100 * scores[i][1] / sum
            recommendations.append((symbol, recommended_percent))
            print(f"{recommended_percent:.2f}% in {symbol}    Score: {scores[i][1]:.2f}")

        winsound.Beep(300, 500)

        if plot:
            fig.tight_layout()
            fig.autofmt_xdate()

            #growth_plot.plot([parse("1/1/2000"), today], [0, 0], label='Zero')
            total_yield_plot.plot([parse("1/1/2000"), today], [0, 0], label='Zero')

            dividend_plot.set_ylim(bottom=min_dividend, top=max_dividend)
            #growth_plot.set_ylim(bottom=min_growth, top=max_growth)
            total_yield_plot.set_ylim(bottom=min_total, top=max_total)

            dividend_plot.tick_params(labelleft=True, left=True, right=True, bottom=True)
            #growth_plot.tick_params(labelleft=True, left=True, right=True, bottom=True)
            total_yield_plot.tick_params(labelleft=True, left=True, right=True, bottom=True, labelbottom=True)

            dividend_plot.legend()
            #growth_plot.legend()
            #total_yield_plot.legend()
            plt.show()