Exemple #1
0
def test_validate_stock():
    bad_stocks = [None, "AB", "---"]
    for stock in bad_stocks:
        with pytest.raises(AssertionError):
            validate_stock(stock)
    good_stocks = ["ABC", "ABCDE", "AB2", "abcde", "ANZ"]
    for stock in good_stocks:
        validate_stock(stock)
Exemple #2
0
def get_dataset(dataset_wanted, request, timeframe=None):
    assert (dataset_wanted in set(["market_sentiment", "eps-per-sector"])
            or dataset_wanted.startswith("kmeans-")
            or dataset_wanted.startswith("financial-metrics-")
            or dataset_wanted.startswith("stock-quotes-"))

    if timeframe is None:
        timeframe = Timeframe(past_n_days=300)

    if dataset_wanted == "market_sentiment":
        df = cached_all_stocks_cip(timeframe)
        return df
    elif dataset_wanted == "kmeans-watchlist":
        _, _, _, _, df = make_kmeans_cluster_dataframe(
            timeframe, 7, user_watchlist(request.user))
        return df
    elif dataset_wanted == "kmeans-etfs":
        _, _, _, _, df = make_kmeans_cluster_dataframe(timeframe, 7,
                                                       all_etfs())
        return df
    elif dataset_wanted.startswith("stock-quotes-"):
        stock = dataset_wanted[len("stock-quotes-"):]
        validate_stock(stock)
        df = company_prices([stock],
                            timeframe=timeframe,
                            fields=all_stock_fundamental_fields,
                            missing_cb=None)
        df['stock_code'] = stock
        return df
    elif dataset_wanted.startswith("kmeans-sector-"):
        sector_id = int(dataset_wanted[14:])
        sector = Sector.objects.get(sector_id=sector_id)
        if sector is None or sector.sector_name is None:
            raise Http404("No stocks associated with sector")
        asx_codes = all_sector_stocks(sector.sector_name)
        _, _, _, _, df = make_kmeans_cluster_dataframe(timeframe, 7, asx_codes)
        return df
    elif dataset_wanted.startswith("financial-metrics-"):
        stock = dataset_wanted[len("financial-metrics-"):]
        validate_stock(stock)
        df = financial_metrics(stock)
        if df is not None:
            # excel doesnt support timezones, so we remove it first
            colnames = [d.strftime("%Y-%m-%d") for d in df.columns]
            df.columns = colnames
            # FALLTHRU
        return df
    elif dataset_wanted == "eps-per-sector":
        df, _ = pe_trends_df(Timeframe(past_n_days=180))
        df = make_pe_trends_eps_df(df, stocks_by_sector())
        df = df.set_index("asx_code", drop=True)
        return df
    else:
        raise ValueError("Unsupported dataset {}".format(dataset_wanted))
Exemple #3
0
def show_stock_sector(request, stock):
    validate_stock(stock)
    validate_user(request.user)

    _, company_details = stock_info(stock, lambda msg: warning(request, msg))
    sector = company_details.sector_name if company_details else None
    all_stocks_cip = cached_all_stocks_cip(Timeframe(past_n_days=180))

    # invoke separate function to cache the calls when we can
    c_vs_s_plot, sector_momentum_plot, sector_companies = analyse_sector_performance(
        stock, sector, all_stocks_cip)
    point_score_plot = net_rule_contributors_plot = None
    if sector_companies is not None:
        point_score_plot, net_rule_contributors_plot = \
                plot_point_scores(stock,
                                  sector_companies,
                                  all_stocks_cip,
                                  default_point_score_rules())

    context = {
        "is_sector":
        True,
        "asx_code":
        stock,
        "sector_momentum_plot":
        sector_momentum_plot,
        "sector_momentum_title":
        "{} sector stocks".format(sector),
        "company_versus_sector_plot":
        c_vs_s_plot,
        "company_versus_sector_title":
        "{} vs. {} performance".format(stock, sector),
        "point_score_plot":
        point_score_plot,
        "point_score_plot_title":
        "Points score due to price movements",
        "net_contributors_plot":
        net_rule_contributors_plot,
        "net_contributors_plot_title":
        "Contributions to point score by rule",
    }
    return render(request, "stock_sector.html", context)
Exemple #4
0
 def get_initial(self, **kwargs):
     stock = kwargs.get("stock", self.kwargs.get("stock"))
     amount = kwargs.get("amount", self.kwargs.get("amount", 5000.0))
     user = self.request.user
     validate_stock(stock)
     validate_user(user)
     quote, latest_date = latest_quote(stock)
     cur_price = quote.last_price
     if cur_price >= 1e-6:
         return {
             "asx_code": stock,
             "user": user,
             "buy_date": latest_date,
             "price_at_buy_date": cur_price,
             "amount": amount,
             "n": int(amount / cur_price),
         }
     else:
         warning(self.request,
                 "Cannot buy {} as its price is zero/unknown".format(stock))
         return {}
Exemple #5
0
def show_fundamentals(request, stock=None, n_days=2 * 365):
    validate_user(request.user)
    validate_stock(stock)
    timeframe = Timeframe(past_n_days=n_days)
    df = company_prices(
        [stock],
        timeframe,
        fields=("eps", "volume", "last_price", "annual_dividend_yield", \
                "pe", "change_in_percent", "change_price", "market_cap", \
                "number_of_shares"),
        missing_cb=None
    )
    #print(df)
    df['change_in_percent_cumulative'] = df['change_in_percent'].cumsum(
    )  # nicer to display cumulative
    df = df.drop('change_in_percent', axis=1)
    fundamentals_plot = plot_fundamentals(df, stock)
    context = {
        "asx_code": stock,
        "is_fundamentals": True,
        "fundamentals_plot": fundamentals_plot
    }
    return render(request, "stock_fundamentals.html", context)
Exemple #6
0
def show_stock(request, stock=None, n_days=2 * 365):
    """
    Displays a view of a single stock via the stock_view.html template and associated state
    """
    validate_stock(stock)
    validate_user(request.user)

    timeframe = Timeframe(
        past_n_days=n_days + 200
    )  # add 200 days so MA 200 can initialise itself before the plotting starts...
    stock_df = rsi_data(
        stock, timeframe)  # may raise 404 if too little data available
    securities, company_details = stock_info(stock,
                                             lambda msg: warning(request, msg))

    momentum_plot = make_rsi_plot(stock, stock_df)

    # plot the price over timeframe in monthly blocks
    prices = stock_df[[
        'last_price'
    ]].transpose()  # use list of columns to ensure pd.DataFrame not pd.Series
    #print(prices)
    monthly_maximum_plot = plot_trend(prices, sample_period='M')

    # populate template and render HTML page with context
    context = {
        "asx_code": stock,
        "securities": securities,
        "cd": company_details,
        "rsi_plot": momentum_plot,
        "is_momentum": True,
        "monthly_highest_price_plot_title": "Maximum price each month trend",
        "monthly_highest_price_plot": monthly_maximum_plot,
        "timeframe": f"{n_days} days",
        "watched": user_watchlist(request.user),
    }
    return render(request, "stock_view.html", context=context)
Exemple #7
0
def toggle_watched(request, stock=None):
    validate_stock(stock)
    validate_user(request.user)
    toggle_watchlist_entry(request.user, stock)
    return redirect_to_next(request)
Exemple #8
0
def test_validate_stock():
    validate_stock('ANZ') # NB: must not assert
    with pytest.raises(AssertionError):
         validate_stock('AN')
Exemple #9
0
def show_financial_metrics(request, stock=None):
    validate_user(request.user)
    validate_stock(stock)

    def data_factory(ld: LazyDictionary):
        data_df = financial_metrics(stock)
        if data_df is None or len(data_df) < 1:
            raise Http404(f"No financial metrics available for {stock}")
        return data_df

    def find_linear_metrics(ld: LazyDictionary) -> Iterable[str]:
        linear_metrics = calculate_trends(ld["data_df"])
        good_linear_metrics = []
        for k, t in linear_metrics.items():
            if t[1] < 0.1:
                good_linear_metrics.append(k)
        return good_linear_metrics

    def find_exp_metrics(ld: LazyDictionary) -> Iterable[str]:
        exp_metrics = calculate_trends(
            ld["data_df"], polynomial_degree=2, nrmse_cutoff=0.05
        )
        good_linear_metrics = set(ld["linear_metrics"])
        good_exp_metrics = []
        for k, t in exp_metrics.items():
            if t[1] < 0.1 and k not in good_linear_metrics:
                good_exp_metrics.append(k)
        return good_exp_metrics

    ld = LazyDictionary()
    ld["data_df"] = lambda ld: data_factory(ld)
    ld["linear_metrics"] = lambda ld: find_linear_metrics(ld)
    ld["exp_metrics"] = lambda ld: find_exp_metrics(ld)

    # print(
    #     f"n_metrics == {len(data_df)} n_trending={len(linear_metrics.keys())} n_good_fit={len(good_linear_metrics)} n_good_exp={len(good_exp_metrics)}"
    # )

    def plot_metrics(df: pd.DataFrame, use_short_labels=False, **kwargs):
        plot = (
            p9.ggplot(df, p9.aes(x="date", y="value", colour="metric"))
            + p9.geom_line(size=1.3)
            + p9.geom_point(size=3)
        )
        if use_short_labels:
            plot += p9.scale_y_continuous(labels=label_shorten)
        n_metrics = df["metric"].nunique()
        return user_theme(
            plot,
            subplots_adjust={"left": 0.2},
            figure_size=(12, int(n_metrics * 1.5)),
            **kwargs,
        )

    def plot_linear_trending_metrics(ld: LazyDictionary):
        df = ld["data_df"].filter(ld["linear_metrics"], axis=0)
        if len(df) < 1:
            return None
        df["metric"] = df.index
        df = df.melt(id_vars="metric").dropna(how="any", axis=0)
        plot = plot_metrics(df, use_short_labels=True)
        plot += p9.facet_wrap("~metric", ncol=1, scales="free_y")
        return plot

    def plot_exponential_growth_metrics(ld: LazyDictionary):
        df = ld["data_df"].filter(ld["exp_metrics"], axis=0)
        if len(df) < 1:
            return None
        df["metric"] = df.index
        df = df.melt(id_vars="metric").dropna(how="any", axis=0)
        plot = plot_metrics(df)
        plot += p9.facet_wrap("~metric", ncol=1, scales="free_y")

        return plot

    def plot_earnings_and_revenue(ld: LazyDictionary):
        df = ld["data_df"].filter(["Ebit", "Total Revenue", "Earnings"], axis=0)
        if len(df) < 2:
            print(f"WARNING: revenue and earnings not availabe for {stock}")
            return None
        df["metric"] = df.index
        df = df.melt(id_vars="metric").dropna(how="any", axis=0)
        plot = plot_metrics(
            df,
            use_short_labels=True,
            legend_position="right",
            y_axis_label="$ AUD",
        )  # need to show metric name somewhere on plot
        return plot

    er_uri = cache_plot(
        f"{stock}-earnings-revenue-plot",
        lambda ld: plot_earnings_and_revenue(ld),
        datasets=ld,
    )
    trending_metrics_uri = cache_plot(
        f"{stock}-trending-metrics-plot",
        lambda ld: plot_linear_trending_metrics(ld),
        datasets=ld,
    )
    exp_growth_metrics_uri = cache_plot(
        f"{stock}-exponential-growth-metrics-plot",
        lambda ld: plot_exponential_growth_metrics(ld),
        datasets=ld,
    )
    warning(
        request,
        "Due to experimental data ingest - data on this page may be wrong/misleading/inaccurate/missing. Use at own risk.",
    )
    context = {
        "asx_code": stock,
        "data": ld["data_df"],
        "earnings_and_revenue_plot_uri": er_uri,
        "trending_metrics_plot_uri": trending_metrics_uri,
        "exp_growth_metrics_plot_uri": exp_growth_metrics_uri,
    }
    return render(request, "stock_financial_metrics.html", context=context)
Exemple #10
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)