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)
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
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
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, )
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)
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)
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)
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)
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)