Example #1
0
def rank_cumulative_change(df: pd.DataFrame, timeframe: Timeframe):
    cum_sum = defaultdict(float)
    # print(df)
    for date in filter(lambda k: k in df.columns, timeframe.all_dates()):
        for code, price_change in df[date].fillna(0.0).iteritems():
            cum_sum[code] += price_change
        rank = pd.Series(cum_sum).rank(method="first", ascending=False)
        df[date] = rank

    all_available_dates = df.columns
    avgs = df.mean(axis=1)  # NB: do this BEFORE adding columns...
    assert len(avgs) == len(df)
    df["x"] = all_available_dates[-1]
    df["y"] = df[all_available_dates[-1]]

    bins = ["top", "bin2", "bin3", "bin4", "bin5", "bottom"]
    average_rank_binned = pd.cut(avgs, len(bins), bins)
    assert len(average_rank_binned) == len(df)
    df["bin"] = average_rank_binned
    df["asx_code"] = df.index
    stock_sector_df = (
        stocks_by_sector()
    )  # make one DB call (cached) rather than lots of round-trips
    # print(stock_sector_df)
    stock_sector_df = stock_sector_df.set_index("asx_code")
    # print(df.index)
    df = df.merge(
        stock_sector_df, left_index=True, right_on="asx_code"
    )  # NB: this merge will lose rows: those that dont have a sector eg. ETF's
    df = pd.melt(
        df,
        id_vars=["asx_code", "bin", "sector_name", "x", "y"],
        var_name="date",
        value_name="rank",
        value_vars=all_available_dates,
    )
    df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d")
    df["x"] = pd.to_datetime(df["x"], format="%Y-%m-%d")
    return df
Example #2
0
def show_pe_trends(request):
    """
    Display a plot of per-sector PE trends across stocks in each sector
    ref: https://www.commsec.com.au/education/learn/choosing-investments/what-is-price-to-earnings-pe-ratio.html
    """
    validate_user(request.user)
    timeframe = Timeframe(past_n_days=180)
    pe_df = company_prices(None,
                           timeframe,
                           fields="pe",
                           missing_cb=None,
                           transpose=True)
    eps_df = company_prices(None,
                            timeframe,
                            fields="eps",
                            missing_cb=None,
                            transpose=True)
    ss = stocks_by_sector()
    ss_dict = {row.asx_code: row.sector_name for row in ss.itertuples()}
    #print(ss_dict)
    eps_stocks = set(eps_df.index)
    n_stocks = len(pe_df)
    positive_pe_stocks = set(pe_df[pe_df.sum(axis=1) > 0.0].index)
    all_stocks = set(pe_df.index)
    n_non_zero_sum = len(positive_pe_stocks)
    #print(exclude_zero_sum)
    records = []
    trading_dates = set(pe_df.columns)

    sector_counts_all_stocks = ss['sector_name'].value_counts()
    all_sectors = set(ss['sector_name'].unique())
    pe_pos_df = pe_df.filter(items=positive_pe_stocks,
                             axis=0).merge(ss,
                                           left_index=True,
                                           right_on='asx_code')
    assert len(pe_pos_df) <= len(positive_pe_stocks) and len(pe_pos_df) > 0
    market_avg_pe_df = pe_pos_df.mean(axis=0).to_frame(
        name='market_pe')  # avg P/E by date series
    market_avg_pe_df['date'] = pd.to_datetime(market_avg_pe_df.index)
    #print(market_avg_pe_df)
    breakdown_by_sector_pe_pos_stocks_only = pe_pos_df[
        'sector_name'].value_counts()
    #print(breakdown_by_sector_pe_pos_stocks_only)
    sector_counts_pe_pos_stocks_only = {
        s[0]: s[1]
        for s in breakdown_by_sector_pe_pos_stocks_only.items()
    }
    #print(sector_counts_pe_pos_stocks_only)
    #print(sector_counts_all_stocks)
    #print(sector_counts_pe_pos_stocks_only)
    for ymd in filter(lambda d: d in trading_dates, timeframe.all_dates(
    )):  # needed to avoid KeyError raised during DataFrame.at[] calls below
        sum_pe_per_sector = defaultdict(float)
        sum_eps_per_sector = defaultdict(float)

        for stock in filter(lambda code: code in ss_dict, all_stocks):
            sector = ss_dict[stock]
            assert isinstance(sector, str)

            if stock in eps_stocks:
                eps = eps_df.at[stock, ymd]
                if isnan(eps):
                    continue
                sum_eps_per_sector[sector] += eps

            if stock in positive_pe_stocks:
                pe = pe_df.at[stock, ymd]
                if isnan(pe):
                    continue
                assert pe >= 0.0
                sum_pe_per_sector[sector] += pe

        #print(sum_pe_per_sector)
        assert len(sector_counts_pe_pos_stocks_only) == len(sum_pe_per_sector)
        assert len(sector_counts_all_stocks) == len(sum_eps_per_sector)
        for sector in all_sectors:
            pe_sum = sum_pe_per_sector.get(sector, None)
            n_pe = sector_counts_pe_pos_stocks_only.get(sector, None)
            pe_mean = pe_sum / n_pe if pe_sum is not None else None
            eps_sum = sum_eps_per_sector.get(sector, None)

            records.append({
                'date': ymd,
                'sector': sector,
                'mean_pe': pe_mean,
                'sum_pe': pe_sum,
                'sum_eps': eps_sum,
                'n_stocks': n_stocks,
                'n_sector_stocks_pe_only': n_pe
            })

    df = pd.DataFrame.from_records(records)
    #print(df[df["sector"] == 'Utilities'])
    #print(df)
    context = {
        "title":
        "PE Trends: {}".format(timeframe.description),
        "n_stocks":
        n_stocks,
        "timeframe":
        timeframe,
        "n_stocks_with_pe":
        n_non_zero_sum,
        "sector_pe_plot":
        plot_sector_field(df, field="mean_pe"),
        "sector_eps_plot":
        plot_sector_field(df, field="sum_eps"),
        "market_pe_plot":
        plot_series(market_avg_pe_df,
                    x='date',
                    y='market_pe',
                    y_axis_label="Market-wide mean P/E",
                    color=None,
                    use_smooth_line=True)
    }
    return render(request, "pe_trends.html", context)
Example #3
0
def show_stock(request, stock=None, n_days=2 * 365):
    """
    Displays a view of a single stock via the template and associated state
    """
    validate_stock(stock)
    validate_user(request.user)
    plot_timeframe = Timeframe(past_n_days=n_days)  # for template

    def dataframe(ld: LazyDictionary) -> pd.DataFrame:
        momentum_timeframe = Timeframe(
            past_n_days=n_days + 200
        )  # to warmup MA200 function
        df = company_prices(
            (stock,),
            momentum_timeframe,
            fields=all_stock_fundamental_fields,
            missing_cb=None,
        )
        return df

    # key dynamic images and text for HTML response. We only compute the required data if image(s) not cached
    # print(df)
    ld = LazyDictionary()
    ld["stock_df"] = lambda ld: ld["stock_df_200"].filter(
        items=plot_timeframe.all_dates(), axis="rows"
    )
    ld["cip_df"] = lambda: cached_all_stocks_cip(plot_timeframe)
    ld["stock_df_200"] = lambda ld: dataframe(ld)
    ld["sector_companies"] = lambda: companies_with_same_sector(stock)
    ld["company_details"] = lambda: stock_info(stock, lambda msg: warning(request, msg))
    ld["sector"] = lambda ld: ld["company_details"].get("sector_name", "")
    # point_score_results is a tuple (point_score_df, net_points_by_rule)
    ld["point_score_results"] = lambda ld: make_point_score_dataframe(
        stock, default_point_score_rules(), ld
    )
    ld["stock_vs_sector_df"] = lambda ld: make_stock_vs_sector_dataframe(
        ld["cip_df"], stock, ld["sector_companies"]
    )
    print(ld["stock_vs_sector_df"])

    momentum_plot = cache_plot(
        f"{plot_timeframe.description}-{stock}-rsi-plot",
        lambda ld: plot_momentum(stock, plot_timeframe, ld),
        datasets=ld,
    )
    monthly_maximum_plot = cache_plot(
        f"{plot_timeframe.description}-{stock}-monthly-maximum-plot",
        lambda ld: plot_trend("M", ld),
        datasets=ld,
    )
    monthly_returns_plot = cache_plot(
        f"{plot_timeframe.description}-{stock}-monthly returns",
        lambda ld: plot_monthly_returns(plot_timeframe, stock, ld),
        datasets=ld,
    )
    company_versus_sector_plot = cache_plot(
        f"{stock}-{ld['sector']}-company-versus-sector",
        lambda ld: plot_company_versus_sector(
            ld["stock_vs_sector_df"], stock, ld["sector"]
        ),
        datasets=ld,
    )

    point_score_plot = cache_plot(
        f"{plot_timeframe.description}-{stock}-point-score-plot",
        lambda ld: plot_series(ld["point_score_results"][0], x="date", y="points"),
        datasets=ld,
    )
    net_rule_contributors_plot = cache_plot(
        f"{plot_timeframe.description}-{stock}-rules-by-points",
        lambda ld: plot_points_by_rule(ld["point_score_results"][1]),
        datasets=ld,
    )

    # populate template and render HTML page with context
    context = {
        "asx_code": stock,
        "watched": user_watchlist(request.user),
        "timeframe": plot_timeframe,
        "information": ld["company_details"],
        "momentum": {
            "rsi_plot": momentum_plot,
            "monthly_highest_price": {
                "title": "Highest price each month",
                "plot_uri": monthly_maximum_plot,
            },
        },
        "fundamentals": {
            "plot_uri": cache_plot(
                f"{stock}-{plot_timeframe.description}-fundamentals-plot",
                lambda ld: plot_fundamentals(
                    fundamentals_dataframe(plot_timeframe, stock, ld),
                    stock,
                ),
                datasets=ld,
            ),
            "title": "Stock fundamentals: EPS, PE, DY etc.",
            "timeframe": plot_timeframe,
        },
        "stock_vs_sector": {
            "plot_uri": company_versus_sector_plot,
            "title": "Company versus sector - percentage change",
            "timeframe": plot_timeframe,
        },
        "point_score": {
            "plot_uri": point_score_plot,
            "title": "Points score due to price movements",
        },
        "net_contributors": {
            "plot_uri": net_rule_contributors_plot,
            "title": "Contributions to point score by rule",
        },
        "month_by_month_return_uri": monthly_returns_plot,
    }
    return render(request, "stock_page.html", context=context)
Example #4
0
def show_purchase_performance(request):
    purchase_buy_dates = []
    purchases = []
    stocks = []
    for stock, purchases_for_stock in user_purchases(request.user).items():
        stocks.append(stock)
        for purchase in purchases_for_stock:
            purchase_buy_dates.append(purchase.buy_date)
            purchases.append(purchase)

    purchase_buy_dates = sorted(purchase_buy_dates)
    # print("earliest {} latest {}".format(purchase_buy_dates[0], purchase_buy_dates[-1]))

    timeframe = Timeframe(from_date=str(purchase_buy_dates[0]),
                          to_date=all_available_dates()[-1])
    df = company_prices(stocks, timeframe, transpose=True)
    rows = []
    stock_count = defaultdict(int)
    stock_cost = defaultdict(float)
    portfolio_cost = 0.0

    for d in [
            datetime.strptime(x, "%Y-%m-%d").date()
            for x in timeframe.all_dates()
    ]:
        d_str = str(d)
        if d_str not in df.columns:  # not a trading day?
            continue
        purchases_to_date = filter(lambda vp, d=d: vp.buy_date <= d, purchases)
        for purchase in purchases_to_date:
            if purchase.buy_date == d:
                portfolio_cost += purchase.amount
                stock_count[purchase.asx_code] += purchase.n
                stock_cost[purchase.asx_code] += purchase.amount

        portfolio_worth = sum_portfolio(df, d_str, stock_count.items())
        #print(df)
        # emit rows for each stock and aggregate portfolio
        for asx_code in stocks:
            cur_price = df.at[asx_code, d_str]
            if np.isnan(cur_price):  # price missing? ok, skip record
                continue
            assert cur_price is not None and cur_price >= 0.0
            stock_worth = cur_price * stock_count[asx_code]

            rows.append({
                "portfolio_cost": portfolio_cost,
                "portfolio_worth": portfolio_worth,
                "portfolio_profit": portfolio_worth - portfolio_cost,
                "stock_cost": stock_cost[asx_code],
                "stock_worth": stock_worth,
                "stock_profit": stock_worth - stock_cost[asx_code],
                "date": d_str,
                "stock": asx_code,
            })

    t = plot_portfolio(pd.DataFrame.from_records(rows))
    portfolio_performance_figure, stock_performance_figure, profit_contributors_figure = t
    context = {
        "title": "Portfolio performance",
        "portfolio_title": "Overall",
        "portfolio_figure": portfolio_performance_figure,
        "stock_title": "Stock",
        "stock_figure": stock_performance_figure,
        "profit_contributors": profit_contributors_figure,
    }
    return render(request, "portfolio_trends.html", context=context)