Beispiel #1
0
def show_trends(request):
    user = request.user
    validate_user(user)
    stocks = user_watchlist(user)
    timeframe = Timeframe(past_n_days=300)
    ld = LazyDictionary()
    ld["cip_df"] = lambda ld: selected_cached_stocks_cip(stocks, timeframe)
    ld["trends"] = lambda ld: calculate_trends(ld["cip_df"])
    ld["rank"] = lambda ld: rank_cumulative_change(
        ld["cip_df"].filter(ld["trends"].keys(), axis="index"), timeframe
    )

    trending_companies_plot = cache_plot(
        f"{user.username}-watchlist-trends",
        lambda ld: plot_company_rank(ld),
        datasets=ld,
    )

    context = {
        "watchlist_trends": ld["trends"],
        "timeframe": timeframe,
        "trending_companies_uri": trending_companies_plot,
        "trending_companies_plot_title": "Trending watchlist stocks (ranked): {}".format(
            timeframe.description
        ),
    }
    return render(request, "watchlist-rank.html", context=context)
Beispiel #2
0
    def get_queryset(self, **kwargs):
        # user never run this view before?
        if kwargs == {}:
            print("WARNING: no form parameters specified - returning empty queryset")
            return Quotation.objects.none()

        self.sector = kwargs.get("sector", self.sector)
        self.sector_id = int(Sector.objects.get(sector_name=self.sector).sector_id)
        wanted_stocks = all_sector_stocks(self.sector)
        print("Found {} stocks matching sector={}".format(len(wanted_stocks), self.sector))
        mrd = latest_quotation_date('ANZ')
        report_top_n = kwargs.get('report_top_n', None)
        report_bottom_n = kwargs.get('report_bottom_n', None)
        if report_top_n is not None or report_bottom_n is not None:
            cip_sum = selected_cached_stocks_cip(wanted_stocks, Timeframe(past_n_days=90)).transpose().sum().to_frame(name="percent_cip")
            #print(cip_sum)
            top_N = set(cip_sum.nlargest(report_top_n, "percent_cip").index) if report_top_n is not None else set()
            bottom_N = set(cip_sum.nsmallest(report_bottom_n, "percent_cip").index) if report_bottom_n is not None else set()
            wanted_stocks = top_N.union(bottom_N)
        print("Requesting valid quotes for {} stocks".format(len(wanted_stocks)))
        self.qs = valid_quotes_only(mrd).filter(asx_code__in=wanted_stocks)
        if len(self.qs) < len(wanted_stocks):
            got = set([q.asx_code for q in self.qs.all()])
            missing_stocks = wanted_stocks.difference(got)
            warning(self.request, f"could not obtain quotes for all stocks as at {mrd}: {missing_stocks}")
        return self.qs
Beispiel #3
0
    def recalc_queryset(self, **kwargs):
        if kwargs == {} or not any(
            ["name" in kwargs, "activity" in kwargs, "sector" in kwargs]):
            return Quotation.objects.none()

        wanted_name = kwargs.get("name", "")
        wanted_activity = kwargs.get("activity", "")
        if len(wanted_name) > 0 or len(wanted_activity) > 0:
            matching_companies = find_named_companies(wanted_name,
                                                      wanted_activity)
        else:
            matching_companies = all_stocks()
        sector = kwargs.get("sector", self.DEFAULT_SECTOR)
        sector_id = int(Sector.objects.get(sector_name=sector).sector_id)
        sector_stocks = all_sector_stocks(sector)
        if kwargs.get("sector_enabled", False):
            matching_companies = matching_companies.intersection(sector_stocks)
        print("Found {} companies matching: name={} or activity={}".format(
            len(matching_companies), wanted_name, wanted_activity))

        report_top_n = kwargs.get("report_top_n", None)
        report_bottom_n = kwargs.get("report_bottom_n", None)
        self.timeframe = Timeframe(past_n_days=90)
        ld = LazyDictionary()
        ld["sector"] = sector
        ld["sector_id"] = sector_id
        ld["sector_companies"] = sector_stocks
        if len(matching_companies) > 0:
            ld["cip_df"] = selected_cached_stocks_cip(matching_companies,
                                                      self.timeframe)
        else:
            ld["cip_df"] = pd.DataFrame()
        ld["sector_performance_df"] = lambda ld: make_sector_performance_dataframe(
            ld["cip_df"], ld["sector_companies"])
        ld["sector_performance_plot"] = lambda ld: self.sector_performance(ld)
        self.ld = ld
        wanted_stocks = self.filter_top_bottom(ld, matching_companies,
                                               report_top_n, report_bottom_n)

        print("Requesting valid quotes for {} stocks".format(
            len(wanted_stocks)))
        quotations_as_at, actual_mrd = valid_quotes_only(
            "latest", ensure_date_has_data=True)
        ret = quotations_as_at.filter(asx_code__in=wanted_stocks)
        if len(ret) < len(wanted_stocks):
            got = set([q.asx_code
                       for q in self.qs.all()]) if self.qs else set()
            missing_stocks = wanted_stocks.difference(got)
            warning(
                self.request,
                f"could not obtain quotes for all stocks as at {actual_mrd}: {missing_stocks}",
            )

        print("Showing results for {} companies".format(
            len(matching_companies)))
        ret, _ = latest_quote(tuple(matching_companies))
        return ret
Beispiel #4
0
def show_outliers(request, stocks, n_days=30, extra_context=None):
    assert stocks is not None
    assert n_days is not None  # typically integer, but desired_dates() is polymorphic
    timeframe = Timeframe(past_n_days=n_days)
    cip = selected_cached_stocks_cip(stocks, timeframe)
    outliers = detect_outliers(stocks, cip)
    extra_context = {
        "title": "Unusual stock behaviours: {}".format(timeframe.description),
        "sentiment_heatmap_title": "Outlier stocks: sentiment",
    }
    return show_companies(
        outliers,
        request,
        timeframe,
        extra_context,
    )
Beispiel #5
0
 def form_valid(self, form):
     sector = form.cleaned_data.get('sector', "Communication Services")
     norm_method = form.cleaned_data.get('normalisation_method', None)
     n_days = form.cleaned_data.get('n_days', 30)
     stocks = all_sector_stocks(sector)
     timeframe = Timeframe(past_n_days=n_days)
     cip = selected_cached_stocks_cip(stocks, timeframe)
     context = self.get_context_data()
     boxplot, winner_results = plot_boxplot_series(cip, normalisation_method=norm_method)
     context.update({
         'title': "Past {} day sector performance: box plot trends".format(n_days),
         'n_days': n_days,
         'sector': sector,
         'plot': boxplot,
         'winning_stocks': winner_results
     })
     return render(self.request, self.template_name, context)
Beispiel #6
0
    def form_valid(self, form):
        sector = form.cleaned_data.get("sector", "Communication Services")
        norm_method = form.cleaned_data.get("normalisation_method", None)
        n_days = form.cleaned_data.get("n_days", 30)
        ld = LazyDictionary()
        ld["stocks"] = lambda ld: all_sector_stocks(sector)
        ld["timeframe"] = Timeframe(past_n_days=n_days)
        ld["cip_df"] = lambda ld: selected_cached_stocks_cip(
            ld["stocks"], ld["timeframe"])

        context = self.get_context_data()

        def winner_results(df: pd.DataFrame) -> list:
            # compute star performers: those who are above the mean on a given day counted over all days
            count = defaultdict(int)
            avg = df.mean(axis=0)
            for col in df.columns:
                winners = df[df[col] > avg[col]][col]
                for winner in winners.index:
                    count[winner] += 1
            results = []
            for asx_code, n_wins in count.items():
                x = df.loc[asx_code].sum()
                # avoid "dead cat bounce" stocks which fall spectacularly and then post major increases in percentage terms
                if x > 0.0:
                    results.append((asx_code, n_wins, x))
            return list(reversed(sorted(results, key=lambda t: t[2])))

        context.update({
            "title":
            "Past {} day sector performance: box plot trends".format(n_days),
            "n_days":
            n_days,
            "sector":
            sector,
            "plot_uri":
            cache_plot(
                f"{sector}-recent-sector-view-{ld['timeframe'].description}-{norm_method}",
                lambda ld: plot_boxplot_series(
                    ld["cip_df"], normalisation_method=norm_method),
                datasets=ld,
            ),
            "winning_stocks":
            winner_results(ld["cip_df"]),
        })
        return render(self.request, self.template_name, context)
Beispiel #7
0
def show_trends(request):
    validate_user(request.user)
    watchlist_stocks = user_watchlist(request.user)
    timeframe = Timeframe(past_n_days=300)
    cip = selected_cached_stocks_cip(watchlist_stocks, timeframe)
    trends = calculate_trends(cip, watchlist_stocks)
    #print(trends)
    # for now we only plot trending companies... too slow and unreadable to load the page otherwise!
    cip = rank_cumulative_change(cip.filter(trends.keys(), axis="index"),
                                 timeframe)
    #print(cip)
    trending_companies_plot = plot_company_rank(cip)
    context = {
        "watchlist_trends":
        trends,
        "trending_companies_plot":
        trending_companies_plot,
        "trending_companies_plot_title":
        "Trending watchlist companies by rank: {}".format(
            timeframe.description),
    }
    return render(request, "trends.html", context=context)
Beispiel #8
0
def show_companies(
        matching_companies, # may be QuerySet or iterable of stock codes (str)
        request,
        sentiment_timeframe: Timeframe,
        extra_context=None,
        template_name="all_stocks.html",
):
    """
    Support function to public-facing views to eliminate code redundancy
    """
    virtual_purchases_by_user = user_purchases(request.user)

    if isinstance(matching_companies, QuerySet):
        stocks_queryset = matching_companies  # we assume QuerySet is already sorted by desired criteria
    elif matching_companies is None or len(matching_companies) > 0:
        stocks_queryset, _ = latest_quote(matching_companies)
        # FALLTHRU

    # sort queryset as this will often be requested by the USER
    sort_by = tuple(request.GET.get("sort_by", "asx_code").split(","))
    info(request, "Sorting by {}".format(sort_by))
    stocks_queryset = stocks_queryset.order_by(*sort_by)

    # keep track of stock codes for template convenience
    asx_codes = [quote.asx_code for quote in stocks_queryset.all()]
    n_top_bottom = extra_context['n_top_bottom'] if 'n_top_bottom' in extra_context else 20
    print("show_companies: found {} stocks".format(len(asx_codes)))
    
    # setup context dict for the render
    context = {
        # NB: title and heatmap_title are expected to be supplied by caller via extra_context
        "timeframe": sentiment_timeframe,
        "title": "Caller must override",
        "watched": user_watchlist(request.user),
        "n_stocks": len(asx_codes),
        "n_top_bottom": n_top_bottom,
        "virtual_purchases": virtual_purchases_by_user,
    }

    # since we sort above, we must setup the pagination also...
    assert isinstance(stocks_queryset, QuerySet)
    paginator = Paginator(stocks_queryset, 50)
    page_number = request.GET.get("page", 1)
    page_obj = paginator.page(page_number)
    context['page_obj'] = page_obj
    context['object_list'] = paginator

    if len(asx_codes) <= 0:
        warning(request, "No matching companies found.")
    else:
        df = selected_cached_stocks_cip(asx_codes, sentiment_timeframe)
        sentiment_heatmap_data, top10, bottom10 = plot_heatmap(df, sentiment_timeframe, n_top_bottom=n_top_bottom)
        sector_breakdown_plot = plot_breakdown(df)
        context.update({
            "best_ten": top10,
            "worst_ten": bottom10,
            "sentiment_heatmap": sentiment_heatmap_data,
            "sentiment_heatmap_title": "{}: {}".format(context['title'], sentiment_timeframe.description),
            "sector_breakdown_plot": sector_breakdown_plot,
        })

    if extra_context:
        context.update(extra_context)
    add_messages(request, context)
    #print(context)
    return render(request, template_name, context=context)
Beispiel #9
0
def show_companies(
    matching_companies,  # may be QuerySet or iterable of stock codes (str)
    request,
    sentiment_timeframe: Timeframe,
    extra_context=None,
    template_name="all_stocks.html",
):
    """
    Support function to public-facing views to eliminate code redundancy
    """
    if isinstance(matching_companies, QuerySet):
        stocks_queryset = matching_companies  # we assume QuerySet is already sorted by desired criteria
    elif matching_companies is None or len(matching_companies) > 0:
        stocks_queryset, _ = latest_quote(matching_companies)
        # FALLTHRU
    else:
        # no companies to report?
        warning(request, "No matching companies.")
        return render(request,
                      template_name,
                      context={"timeframe": sentiment_timeframe})

    # prune companies without a latest price, makes no sense to report them
    stocks_queryset = stocks_queryset.exclude(last_price__isnull=True)

    # sort queryset as this will often be requested by the USER
    arg = request.GET.get("sort_by", "asx_code")
    #info(request, "Sorting by {}".format(arg))

    if arg == "sector" or arg == "sector,-eps":
        ss = {
            s["asx_code"]: s["sector_name"]
            for s in stocks_by_sector().to_dict("records")
        }
        if arg == "sector":
            stocks_queryset = sorted(stocks_queryset,
                                     key=lambda s: ss.get(s.asx_code, "Z")
                                     )  # companies without sector sort last
        else:
            eps_dict = {
                s.asx_code: s.eps if s.eps is not None else 0.0
                for s in stocks_queryset
            }
            stocks_queryset = sorted(
                stocks_queryset,
                key=lambda s:
                (ss.get(s.asx_code, "Z"), -eps_dict.get(s.asx_code, 0.0)),
            )
    else:
        sort_by = tuple(arg.split(","))
        stocks_queryset = stocks_queryset.order_by(*sort_by)

    # keep track of stock codes for template convenience
    asx_codes = [quote.asx_code for quote in stocks_queryset]
    n_top_bottom = (extra_context["n_top_bottom"]
                    if "n_top_bottom" in extra_context else 20)
    print("show_companies: found {} stocks".format(len(asx_codes)))

    # setup context dict for the render
    context = {
        # NB: title and heatmap_title are expected to be supplied by caller via extra_context
        "timeframe": sentiment_timeframe,
        "title": "Caller must override",
        "watched": user_watchlist(request.user),
        "n_stocks": len(asx_codes),
        "n_top_bottom": n_top_bottom,
        "virtual_purchases": user_purchases(request.user),
    }

    # since we sort above, we must setup the pagination also...
    # assert isinstance(stocks_queryset, QuerySet)
    paginator = Paginator(stocks_queryset, 50)
    page_number = request.GET.get("page", 1)
    page_obj = paginator.page(page_number)
    context["page_obj"] = page_obj
    context["object_list"] = paginator

    # compute totals across all dates for the specified companies to look at top10/bottom10 in the timeframe
    ld = LazyDictionary()
    ld["cip_df"] = lambda ld: selected_cached_stocks_cip(
        asx_codes, sentiment_timeframe)
    ld["sum_by_company"] = lambda ld: ld["cip_df"].sum(axis=1,
                                                       numeric_only=True)
    ld["top10"] = lambda ld: ld["sum_by_company"].nlargest(n_top_bottom)
    ld["bottom10"] = lambda ld: ld["sum_by_company"].nsmallest(n_top_bottom)
    ld["stocks_by_sector"] = lambda ld: stocks_by_sector()

    if len(asx_codes) <= 0 or len(ld["top10"]) <= 0:
        warning(request, "No matching companies found.")
    else:
        sorted_codes = "-".join(sorted(asx_codes))
        sentiment_heatmap_uri = cache_plot(
            f"{sorted_codes}-{sentiment_timeframe.description}-stocks-sentiment-plot",
            lambda ld: plot_heatmap(sentiment_timeframe, ld),
            datasets=ld,
        )

        key = f"{sorted_codes}-{sentiment_timeframe.description}-breakdown-plot"
        sector_breakdown_uri = cache_plot(key, plot_breakdown, datasets=ld)

        top10_plot_uri = cache_plot(
            f"top10-plot-{'-'.join(ld['top10'].index)}",
            lambda ld: plot_cumulative_returns(ld["top10"].index, ld),
            datasets=ld,
        )
        bottom10_plot_uri = cache_plot(
            f"bottom10-plot-{'-'.join(ld['bottom10'].index)}",
            lambda ld: plot_cumulative_returns(ld["bottom10"].index, ld),
            datasets=ld,
        )

        context.update({
            "best_ten":
            ld["top10"],
            "worst_ten":
            ld["bottom10"],
            "sentiment_heatmap_uri":
            sentiment_heatmap_uri,
            "sentiment_heatmap_title":
            "{}: {}".format(context["title"], sentiment_timeframe.description),
            "sector_breakdown_uri":
            sector_breakdown_uri,
            "top10_plot_uri":
            top10_plot_uri,
            "bottom10_plot_uri":
            bottom10_plot_uri,
            "timeframe_end_performance":
            timeframe_end_performance(ld),
        })

    if extra_context:
        context.update(extra_context)
    add_messages(request, context)
    # print(context)
    return render(request, template_name, context=context)