def recalc_queryset(self, **kwargs): n_days = kwargs.get("n_days", 30) stocks_to_consider = filter_stocks_to_search( self.request, kwargs.get("what_to_search")) period1 = kwargs.get("period1", 20) period2 = kwargs.get("period2", 200) matching_stocks = set() self.timeframe = Timeframe(past_n_days=n_days) assert period2 > period1 df = company_prices(stocks_to_consider, Timeframe(past_n_days=n_days + period2), transpose=False) # print(df) wanted_dates = set(self.timeframe.all_dates()) for s in filter(lambda asx_code: asx_code in df.columns, stocks_to_consider): last_price = df[s] # we filter now because it is after the warm-up period for MA200.... ma20 = last_price.rolling(period1).mean().filter( items=wanted_dates, axis=0) ma200 = (last_price.rolling(period2, min_periods=min([ 50, 3 * period1 ])).mean().filter(items=wanted_dates, axis=0)) matching_dates = set( [xo[1] for xo in calc_ma_crossover_points(ma20, ma200)]) if len(matching_dates.intersection(wanted_dates)) > 0: matching_stocks.add(s) return list(matching_stocks)
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))
def test_timeframe(data, expected): #print(data) tf = Timeframe(**data, today=datetime(year=2021, month=3, day=31).date()) # note today is fixed date for easier testing all_dates = tf.all_dates() assert len(all_dates) == expected[0] assert tf.n_days == expected[0] assert tf.n_days == len(tf) assert str(tf) == f"Timeframe: {data}" assert tf.description == expected[1] all_dates_expected = expected[2] if all_dates_expected is not None: assert all_dates == all_dates_expected assert all_dates_expected[-1] in tf assert not '1999-01-01' in tf validate_date(tf.most_recent_date)
def test_company_prices(quotation_fixture, monkeypatch): #expected_dates = ['2021-01-01', '2021-01-02', '2021-01-03', '2021-01-04', '2021-01-05', '2021-01-06'] monkeypatch.setattr(mdl, 'make_superdf', mock_superdf_all_stocks) # basic check required_timeframe = Timeframe(from_date='2021-01-01', n=6) df = company_prices(['ABC', 'OTHER'], required_timeframe, fields='last_price', missing_cb=None, transpose=True) assert isinstance(df, pd.DataFrame) assert len(df) == 2 assert list(df.index) == ['ABC', 'OTHER'] assert list(df.columns) == ['2021-01-01', '2021-01-02', '2021-01-03', '2021-01-04', '2021-01-05', '2021-01-06'] is_other_nan = list(np.isnan(df.loc['OTHER'])) assert is_other_nan == [False, True, True, True, True, True] # check impute missing functionality df2 = company_prices(['ABC', 'OTHER'], required_timeframe, fields='last_price', transpose=True) assert list(df2.loc['OTHER']) == [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] # finally check that a multi-field DataFrame is as requested monkeypatch.setattr(mdl, 'make_superdf', mock_superdf_many_fields)
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['sector'] = [stock_sector_df.loc[code].sector_name for code in df.index] df = pd.melt(df, id_vars=['asx_code', 'bin', 'sector', '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
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 form_valid(self, form): exclude = form.cleaned_data["excluded_stocks"] n_days = form.cleaned_data["n_days"] algo = form.cleaned_data["method"] portfolio_cost = form.cleaned_data["portfolio_cost"] exclude_price = form.cleaned_data.get("exclude_price", None) max_stocks = form.cleaned_data.get("max_stocks", 80) stocks = self.stocks() if exclude is not None: if isinstance(exclude, str): exclude = exclude.split(",") stocks = set(stocks).difference(exclude) if form.cleaned_data["exclude_etfs"]: stocks = set(stocks).difference(all_etfs()) print(f"After excluding ETFs: {len(stocks)} stocks remain") self.timeframe = Timeframe(past_n_days=n_days) self.results = optimise_portfolio( stocks, self.timeframe, algo=algo, max_stocks=max_stocks, total_portfolio_value=portfolio_cost, exclude_price=exclude_price, warning_cb=lambda msg: warning(self.request, msg), returns_by=form.cleaned_data.get("returns_by", None), ) return render(self.request, self.template_name, self.get_context_data())
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)
class DividendYieldSearch( SearchMixin, LoginRequiredMixin, MultipleObjectTemplateResponseMixin, FormView, ): form_class = DividendSearchForm template_name = "search_form.html" # generic template, not specific to this view action_url = "/search/by-yield" ordering = ("-annual_dividend_yield",) timeframe = Timeframe(past_n_days=30) qs = None def additional_context(self, context): """ Return the additional fields to be added to the context by render_to_response(). Subclasses should override this rather than the template design pattern implementation of render_to_response() """ assert context is not None return { "title": "Find by dividend yield or P/E", "sentiment_heatmap_title": "Matching stock heatmap: {}".format(self.timeframe.description), "n_top_bottom": 20 } def render_to_response(self, context, **kwargs): """ Invoke show_companies() """ assert kwargs is not None context.update(self.additional_context(context)) return show_companies( # will typically invoke show_companies() to share code across all views self.qs, self.request, self.timeframe, context, template_name=self.template_name ) def get_queryset(self, **kwargs): if kwargs == {}: return Quotation.objects.none() as_at = latest_quotation_date('ANZ') min_yield = kwargs.get("min_yield") if "min_yield" in kwargs else 0.0 max_yield = kwargs.get("max_yield") if "max_yield" in kwargs else 10000.0 results = Quotation.objects.filter(fetch_date=as_at).\ filter(annual_dividend_yield__gte=min_yield).\ filter(annual_dividend_yield__lte=max_yield) if "min_pe" in kwargs: results = results.filter(pe__gte=kwargs.get("min_pe")) if "max_pe" in kwargs: results = results.filter(pe__lt=kwargs.get("max_pe")) if "min_eps_aud" in kwargs: results = results.filter(eps__gte=kwargs.get("min_eps_aud")) self.qs = results return self.qs
def show_total_earnings(request): validate_user(request.user) def data_factory(df: pd.DataFrame) -> pd.DataFrame: df = df.pivot( index=["asx_code", "fetch_date"], columns="field_name", values="field_value" ) required = (df.number_of_shares > 0) & (df.eps > 0.0) df = df[required] # ignore stocks which have unknowns # print(df) df["total_earnings"] = df["eps"] * df["number_of_shares"] df = df.dropna(how="any", axis=0) df = df.reset_index() df = df.pivot(index="asx_code", columns="fetch_date", values="total_earnings") df = df.merge(stocks_by_sector(), left_index=True, right_on="asx_code") df = df.set_index("asx_code", drop=True) df = df.groupby("sector_name").sum() df["sector_name"] = df.index df = df.melt(id_vars="sector_name", var_name="fetch_date") assert set(df.columns) == set(["sector_name", "fetch_date", "value"]) df["fetch_date"] = pd.to_datetime(df["fetch_date"], format="%Y-%m-%d") return df def plot(df: pd.DataFrame) -> p9.ggplot: plot = ( p9.ggplot( df, p9.aes( x="fetch_date", y="value", color="sector_name", # group="sector_name" ), ) + p9.geom_line(size=1.2) + p9.facet_wrap("~sector_name", ncol=2, scales="free_y") + p9.scale_y_continuous(labels=label_shorten) ) return user_theme( plot, y_axis_label="Total sector earnings ($AUD, positive contributions only)", figure_size=(12, 14), subplots_adjust={"wspace": 0.25}, ) ld = LazyDictionary() ld["timeframe"] = Timeframe(past_n_days=180) ld["pe_trends_df"] = lambda ld: pe_trends_df(ld["timeframe"]) ld["df"] = lambda ld: data_factory(ld["pe_trends_df"]) context = { "title": "Earnings per sector over time", "timeframe": ld["timeframe"], "plot_uri": cache_plot( f"total-earnings-by-sector:{ld['timeframe'].description}", lambda ld: plot(ld["df"]), datasets=ld, ), } return render(request, "total_earnings_by_sector.html", context=context)
def get_dataset(dataset_wanted): assert dataset_wanted in set(["market_sentiment"]) if dataset_wanted == "market_sentiment": df = cached_all_stocks_cip(Timeframe()) return df else: raise ValueError("Unsupported dataset {}".format(dataset_wanted))
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 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
def make_portfolio_performance_dataframe( stocks: Iterable[str], timeframe: Timeframe, purchases: Iterable[VirtualPurchase]) -> pd.DataFrame: def sum_portfolio(df: pd.DataFrame, date_str: str, stock_items): validate_date(date_str) portfolio_worth = sum( map(lambda t: df.at[t[0], date_str] * t[1], stock_items)) return portfolio_worth 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, }) df = pd.DataFrame.from_records(rows) df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d") return df
def market_sentiment(request, n_days=21, n_top_bottom=20, sector_n_days=180): validate_user(request.user) assert n_days > 0 assert n_top_bottom > 0 timeframe = Timeframe(past_n_days=n_days) sector_timeframe = Timeframe(past_n_days=sector_n_days) df = cached_all_stocks_cip(timeframe) sector_df = cached_all_stocks_cip(sector_timeframe) sentiment_plot, top10, bottom10 = plot_heatmap(df, timeframe, n_top_bottom=n_top_bottom) sector_performance_plot = plot_market_wide_sector_performance(sector_df) context = { "sentiment_data": sentiment_plot, "n_days": timeframe.n_days, "n_stocks_plotted": len(df), "n_top_bottom": n_top_bottom, "best_ten": top10, "worst_ten": bottom10, "watched": user_watchlist(request.user), "sector_performance": sector_performance_plot, "sector_performance_title": "Cumulative sector avg. performance: {}".format( sector_timeframe.description), "title": "Market sentiment: {}".format(timeframe.description), "market_cap_distribution_plot": plot_market_cap_distribution(tuple(df.index), latest_quotation_date('ANZ'), sector_df.columns[0]) } return render(request, "market_sentiment_view.html", context=context)
def show_all_stocks(request): all_dates = all_available_dates() if len(all_dates) < 1: raise Http404("No ASX price data available!") ymd = all_dates[-1] validate_date(ymd) qs = valid_quotes_only(ymd) timeframe = Timeframe() return show_companies(qs, request, timeframe, extra_context={ "title": "All stocks", "sentiment_heatmap_title": "All stock sentiment: {}".format(timeframe.description) })
def show_watched(request): validate_user(request.user) matching_companies = user_watchlist(request.user) timeframe = Timeframe() return show_companies( matching_companies, request, timeframe, { "title": "Stocks you are watching", "sentiment_heatmap_title": "Watchlist stocks sentiment: {}".format(timeframe.description), })
def process_form(self, cleaned_data): timeframe = Timeframe(past_n_days=cleaned_data.get("timeframe", 180)) bond = cleaned_data.get("bond_name", None) df = get_bond_prices(bond, timeframe) plot_uri = cache_plot( f"{bond}-{timeframe.description}", lambda ld: self.make_plot(df, timeframe), ) return { "title": "Visualise bond yields by country", "plot_uri": "/png/" + plot_uri, "plot_title": f"Bond prices: {bond} over {timeframe.description}", }
def show_increasing_yield_stocks(request): validate_user(request.user) matching_companies = increasing_yield(None) extra_context = { "title": "Stocks with increasing yield over past 300 days", "sentiment_heatmap_title": "Sentiment for selected stocks", } return show_companies( matching_companies, request, Timeframe(), extra_context, )
def show_etfs(request): validate_user(request.user) matching_codes = all_etfs() extra_context = { "title": "Exchange Traded funds over past 300 days", "sentiment_heatmap_title": "Sentiment for ETFs", } return show_companies( matching_codes, request, Timeframe(), extra_context, )
class MomentumSearch(DividendYieldSearch): """ Search for momentum related signals by finding cross over points between 20-day moving average and 200 day moving average. We try to provide a warm-up period of data (depending on what it is in the database) so that the user-requested period has good data. """ form_class = MomentumSearchForm action_url = "/search/momentum-change" template_name = "search_form.html" def additional_context(self, context): ret = super().additional_context(context) ret.update({ "title": "Momentum Search", "sentiment_heatmap_title": "Momentum stock sentiment", }) return ret def recalc_queryset(self, **kwargs): n_days = kwargs.get("n_days", 30) stocks_to_consider = filter_stocks_to_search( self.request, kwargs.get("what_to_search")) period1 = kwargs.get("period1", 20) period2 = kwargs.get("period2", 200) matching_stocks = set() self.timeframe = Timeframe(past_n_days=n_days) assert period2 > period1 df = company_prices(stocks_to_consider, Timeframe(past_n_days=n_days + period2), transpose=False) # print(df) wanted_dates = set(self.timeframe.all_dates()) for s in filter(lambda asx_code: asx_code in df.columns, stocks_to_consider): last_price = df[s] # we filter now because it is after the warm-up period for MA200.... ma20 = last_price.rolling(period1).mean().filter( items=wanted_dates, axis=0) ma200 = (last_price.rolling(period2, min_periods=min([ 50, 3 * period1 ])).mean().filter(items=wanted_dates, axis=0)) matching_dates = set( [xo[1] for xo in calc_ma_crossover_points(ma20, ma200)]) if len(matching_dates.intersection(wanted_dates)) > 0: matching_stocks.add(s) return list(matching_stocks)
def show_watched(request): validate_user(request.user) matching_companies = user_watchlist(request.user) timeframe = Timeframe() return show_companies( matching_companies, request, timeframe, { "title": "Stocks you are watching", "sentiment_heatmap_title": "Watchlist sentiment heatmap", }, )
def get_queryset(self, **kwargs): if any([kwargs == {}, "threshold" not in kwargs, "timeframe_in_days" not in kwargs]): return Quotation.objects.none() threshold_percentage = kwargs.get("threshold") self.timeframe = Timeframe(past_n_days=kwargs.get("timeframe_in_days", 30)) df = find_movers( threshold_percentage, self.timeframe, kwargs.get("show_increasing", False), kwargs.get("show_decreasing", False), kwargs.get("max_price", None) ) self.qs, _ = latest_quote(tuple(df.index)) return self.qs
def process_form(self, cleaned_data: dict) -> dict: timeframe = Timeframe(past_n_days=cleaned_data["timeframe"]) crypto_symbol = cleaned_data.get("currency", "BTC") crypto_prices = get_crypto_prices(crypto_symbol, timeframe) # print(crypto_prices) plot_uri = cache_plot( f"{crypto_symbol}-{timeframe.description}", lambda ld: self.make_plot(crypto_prices, timeframe), ) return { "title": "Visualize cryptocurrency prices over time", "plot_uri": "/png/" + plot_uri, "plot_title": f"{crypto_symbol} over {timeframe.description}", }
def process_form(self, cleaned_data): timeframe = Timeframe(past_n_days=cleaned_data.get("timeframe", 180)) commodity_str = cleaned_data.get("commodity", "Gold") df = get_commodity_prices(commodity_str, timeframe) plot_uri = cache_plot( f"{commodity_str}-{timeframe.description}", lambda ld: self.make_plot(df, timeframe), ) return { "title": "Visualize commodity prices over time", "plot_uri": "/png/" + plot_uri, "plot_title": f"Commodity prices: {commodity_str} over {timeframe.description}", }
def render_to_response(self, context): context.update({ "title": "Find companies by financial metric", "sentiment_heatmap_title": "Matching stock sentiment", }) warning( self.request, "Due to experimental data ingest, results may be wrong/inaccurate/misleading. Use at own risk", ) return show_companies( self.object_list, # ie. return result from self.get_queryset() self.request, Timeframe(past_n_days=30), context, self.template_name, )
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_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)