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)
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 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)
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 {}
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)
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)
def toggle_watched(request, stock=None): validate_stock(stock) validate_user(request.user) toggle_watchlist_entry(request.user, stock) return redirect_to_next(request)
def test_validate_stock(): validate_stock('ANZ') # NB: must not assert with pytest.raises(AssertionError): validate_stock('AN')
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)
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)