Fix stocks asthetic style (#1355)

* Fix Theo's comment

* Fix tests failing

* Fix tests

* Make plot lines thinner

* Make ticks thinner globally by default

* Apply global style when any controller is initialized

Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com>
This commit is contained in:
minhhoang1023
2022-02-14 23:11:05 +07:00
committed by GitHub
parent 6c9999a9c8
commit 53945ddbae
16 changed files with 197 additions and 96 deletions

View File

@@ -27,6 +27,7 @@ from gamestonk_terminal.helper_funcs import (
parse_known_args_and_warn,
valid_date_in_past,
)
from gamestonk_terminal.config_terminal import theme
from gamestonk_terminal.rich_config import console
from gamestonk_terminal.stocks import stocks_helper
from gamestonk_terminal.cryptocurrency import cryptocurrency_helpers
@@ -94,6 +95,8 @@ class BaseController(metaclass=ABCMeta):
)
self.parser.add_argument("cmd", choices=self.controller_choices)
theme.applyMPLstyle()
def check_path(self) -> None:
path = self.PATH
if path[0] != "/":

View File

@@ -64,8 +64,8 @@ def display_sentiment_compare(
for idx, tick in enumerate(similar):
offset = 2 * idx
ax.axhline(y=offset, color="grey", linestyle="--", lw=2)
ax.axhline(y=offset + 1, color="grey", linestyle="--", lw=1)
ax.axhline(y=offset, color="white", linestyle="--", lw=1)
ax.axhline(y=offset + 1, color="white", linestyle="--", lw=1)
senValues = np.array(pd.to_numeric(df_sentiment[tick].values))
senNone = np.array(0 * len(df_sentiment))
@@ -74,7 +74,6 @@ def display_sentiment_compare(
pd.to_numeric(df_sentiment[tick].values) + offset,
offset,
where=(senValues < senNone),
alpha=0.60,
color=theme.down_color,
interpolate=True,
)
@@ -84,15 +83,13 @@ def display_sentiment_compare(
pd.to_numeric(df_sentiment[tick].values) + offset,
offset,
where=(senValues >= senNone),
alpha=0.60,
color=theme.up_color,
interpolate=True,
)
ax.set_ylabel("Sentiment")
ax.axhline(y=-1, color="grey", linestyle="--", lw=1)
ax.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2)
ax.minorticks_on()
ax.axhline(y=-1, color="white", linestyle="--", lw=1)
ax.set_yticks(np.arange(len(similar)) * 2)
ax.set_yticklabels(similar)
ax.set_xlim(df_sentiment.index[0], df_sentiment.index[-1])
@@ -172,7 +169,9 @@ def display_sentiment_correlation(
ax=ax,
)
similar_string = ",".join(similar)
ax.set_title(f"Correlation heatmap across {similar_string}")
ax.set_title(
f"Sentiment correlation heatmap across \n{similar_string}", fontsize=11
)
if not external_axes:
theme.visualize_output()

View File

@@ -3,7 +3,7 @@ __docformat__ = "numpy"
import logging
from datetime import datetime, timedelta
from typing import List
from typing import List, Optional
import matplotlib.pyplot as plt
import pandas as pd
@@ -11,10 +11,11 @@ import yfinance as yf
from sklearn.manifold import TSNE
from sklearn.preprocessing import normalize
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.config_terminal import theme
from gamestonk_terminal.config_plot import PLOT_DPI
from gamestonk_terminal.decorators import log_start_end
from gamestonk_terminal.helper_funcs import plot_autoscale
from gamestonk_terminal.rich_config import console
logger = logging.getLogger(__name__)
@@ -84,7 +85,11 @@ def get_1y_sp500() -> pd.DataFrame:
@log_start_end(log=logger)
def get_sp500_comps_tsne(
ticker: str, lr: int = 200, no_plot: bool = False, num_tickers: int = 10
ticker: str,
lr: int = 200,
no_plot: bool = False,
num_tickers: int = 10,
external_axes: Optional[List[plt.Axes]] = None,
) -> List[str]:
"""
Runs TSNE on SP500 tickers (along with ticker if not in SP500).
@@ -125,20 +130,61 @@ def get_sp500_comps_tsne(
xs = tsne_features[:, 0]
ys = tsne_features[:, 1]
if not no_plot:
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
ax.scatter(xs, ys, alpha=0.5)
for x, y, company in zip(xs, ys, rets.index):
if company != ticker:
ax.annotate(company, (x, y), fontsize=9, alpha=0.75)
else:
ax.scatter(x, y, s=50, c="r")
ax.annotate(company, (x, y), fontsize=9, alpha=1)
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
plt.show()
data = pd.DataFrame({"X": xs, "Y": ys}, index=rets.index)
x0, y0 = data.loc[ticker]
data["dist"] = (data.X - x0) ** 2 + (data.Y - y0) ** 2
data = data.sort_values(by="dist")
return data.iloc[1 : num_tickers + 1].index.to_list()
# This plot has 1 axis
if not external_axes:
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
else:
if len(external_axes) != 1:
console.print("[red]Expected list of one axis item./n[/red]")
return []
(ax,) = external_axes
data = pd.DataFrame({"X": xs, "Y": ys}, index=rets.index)
x0, y0 = data.loc[ticker]
data["dist"] = (data.X - x0) ** 2 + (data.Y - y0) ** 2
data = data.sort_values(by="dist")
ticker_df = data[data.index == ticker]
top_n = data.iloc[1 : (num_tickers + 1)]
top_n_name = top_n.index.to_list()
top_100 = data[(num_tickers + 1) : 101]
ax.scatter(
top_n.X,
top_n.Y,
alpha=0.8,
c=theme.up_color,
label=f"Top {num_tickers} closest tickers",
)
ax.scatter(
top_100.X, top_100.Y, alpha=0.5, c="grey", label="Top 100 closest tickers"
)
for x, y, company in zip(top_n.X, top_n.Y, top_n.index):
ax.annotate(company, (x, y), fontsize=9, alpha=0.9)
for x, y, company in zip(top_100.X, top_100.Y, top_100.index):
ax.annotate(company, (x, y), fontsize=9, alpha=0.75)
ax.scatter(
ticker_df.X,
ticker_df.Y,
s=50,
c=theme.down_color,
)
ax.annotate(ticker, (ticker_df.X, ticker_df.Y), fontsize=9, alpha=1)
ax.legend()
ax.set_title(
f"Top 100 closest stocks on S&P500 to {ticker} using TSNE algorithm",
fontsize=11,
)
ax.set_xlabel("Dimension 1")
ax.set_ylabel("Dimension 2")
theme.style_primary_axis(ax)
if not external_axes:
theme.visualize_output()
return top_n_name

View File

@@ -89,12 +89,14 @@ def display_historical(
return
(ax,) = external_axes
ax.plot(df_similar)
# breakpoint()
companies_names = df_similar.columns.to_list()
ax.plot(df_similar, label=companies_names)
ax.set_title("Historical price of similar companies")
ax.set_ylabel(f"{['','Normalized'][normalize]} Share Price {['($)',''][normalize]}")
# ensures that the historical data starts from same datapoint
ax.set_xlim([df_similar.index[0], df_similar.index[-1]])
ax.legend(loc="best")
theme.style_primary_axis(ax)
if not external_axes:
@@ -139,13 +141,15 @@ def display_volume(
(ax,) = external_axes
df_similar = df_similar.div(1_000_000)
companies_names = df_similar.columns.to_list()
ax.plot(df_similar)
ax.plot(df_similar, label=companies_names)
ax.set_title("Historical volume of similar companies")
ax.set_ylabel("Volume [M]")
# ensures that the historical data starts from same datapoint
ax.set_xlim([df_similar.index[0], df_similar.index[-1]])
ax.legend()
theme.style_primary_axis(ax)
if not external_axes:

View File

@@ -108,6 +108,7 @@ def plot_dark_pools(
ax2.set_ylabel("Shares per Trade")
ax2.xaxis.set_major_formatter(mdates.DateFormatter("%m/%d"))
ax2.xaxis.set_major_locator(mdates.DayLocator(interval=4))
ax2.set_xlim(otc.index[0], otc.index[-1])
ax2.set_xlabel("Weeks")
theme.style_primary_axis(ax1)
@@ -191,6 +192,8 @@ def plot_dark_pools_ats(
ax.set_ylabel("Total Weekly Shares [Million]")
ax.set_title("Dark Pool (ATS) growing tickers")
ax.set_xlabel("Weeks")
ats["weekStartDate"] = pd.to_datetime(ats["weekStartDate"])
ax.set_xlim(ats["weekStartDate"].iloc[0], ats["weekStartDate"].iloc[-1])
theme.style_primary_axis(ax)
if not external_axes:

View File

@@ -86,6 +86,11 @@ def display_short_by_exchange(
# remove the scientific notion on the left hand side
ax.ticklabel_format(style="plain", axis="y")
ax.set_xlim(
volume_by_exchange.Date.iloc[0],
volume_by_exchange.Date.iloc[-1],
)
ax.set_title(f"Net Short Volume for {ticker}")
theme.style_primary_axis(ax)

View File

@@ -71,9 +71,11 @@ def fails_to_deliver(
ax1.bar(
ftds_data["SETTLEMENT DATE"],
ftds_data["QUANTITY (FAILS)"] / 1000,
label="Fail Quantity",
)
ax1.set_ylabel("Shares [K]")
ax1.set_title(f"Fails-to-deliver Data for {ticker}")
ax1.legend(loc="lower right")
if num > 0:
stock_ftd = stock[stock.index > (datetime.now() - timedelta(days=num + 31))]
@@ -81,8 +83,11 @@ def fails_to_deliver(
stock_ftd = stock[stock.index > start]
stock_ftd = stock_ftd[stock_ftd.index < end]
ax2.plot(stock_ftd.index, stock_ftd["Adj Close"], color="tab:orange")
ax2.plot(
stock_ftd.index, stock_ftd["Adj Close"], color="orange", label="Share Price"
)
ax2.set_ylabel("Share Price [$]")
ax2.legend(loc="upper right")
theme.style_twin_axes(ax1, ax2)

View File

@@ -197,7 +197,6 @@ def short_interest_volume(
df["total_volume"] / 1_000_000,
width=timedelta(days=1),
color=theme.up_color,
alpha=0.4,
label="Total Volume",
)
ax.bar(
@@ -205,7 +204,6 @@ def short_interest_volume(
df["short_volume"] / 1_000_000,
width=timedelta(days=1),
color=theme.down_color,
alpha=0.4,
label="Short Volume",
)
@@ -327,7 +325,6 @@ def net_short_position(
df["dates"],
df["dollar_net_volume"] / 1_000,
color=theme.down_color,
alpha=0.4,
label="Net Short Vol. (1k $)",
)
ax1.set_ylabel("Net Short Vol. (1k $)")

View File

@@ -85,10 +85,12 @@ def price_target_from_analysts(
df_analyst_data = df_analyst_data[start:] # type: ignore
if interval == "1440min":
ax.plot(stock.index, stock["Adj Close"].values, lw=3)
ax.plot(stock.index, stock["Adj Close"].values)
legend_price_label = "Adjusted closing price"
# Intraday
else:
ax.plot(stock.index, stock["Close"].values, lw=3)
ax.plot(stock.index, stock["Close"].values)
legend_price_label = "Closing price"
if start:
ax.plot(df_analyst_data.groupby(by=["Date"]).mean()[start:]) # type: ignore
@@ -96,10 +98,14 @@ def price_target_from_analysts(
ax.plot(df_analyst_data.groupby(by=["Date"]).mean())
ax.scatter(
df_analyst_data.index, df_analyst_data["Price Target"], c="red", s=40
df_analyst_data.index,
df_analyst_data["Price Target"],
color=theme.down_color,
edgecolors=theme.up_color,
zorder=2,
)
ax.legend(["Closing Price", "Average Price Target", "Price Target"])
ax.legend([legend_price_label, "Average Price Target", "Price Target"])
ax.set_title(f"{ticker} (Time Series) and Price Target")
ax.set_xlim(stock.index[0], stock.index[-1])

View File

@@ -191,11 +191,12 @@ def display_dividends(
mfc=theme.down_color,
mec=theme.down_color,
alpha=1,
label="Dividends Payout",
)
ax.set_ylabel("Amount ($)")
ax.set_title(f"Dividend History for {ticker}")
ax.set_xlim(div_history.index[-1], div_history.index[0])
ax.legend()
theme.style_primary_axis(ax)
if not external_axes:

View File

@@ -11,6 +11,7 @@ from datetime import datetime, timedelta
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import matplotlib
from gamestonk_terminal.config_terminal import theme
from gamestonk_terminal.config_plot import PLOT_DPI
@@ -203,9 +204,10 @@ def display_government_buys(
return
(ax,) = external_axes
colors = theme.get_colors()
df_gov.groupby("Ticker")["upper"].sum().div(1000).sort_values(ascending=False).head(
n=num
).plot(kind="bar", rot=0, ax=ax)
).plot(kind="bar", rot=0, ax=ax, color=colors)
ax.set_ylabel("Amount [1k $]")
ax.set_title(
@@ -321,15 +323,15 @@ def display_government_sells(
return
(ax,) = external_axes
colors = theme.get_colors()
df_gov.groupby("Ticker")["upper"].sum().div(1000).sort_values().abs().head(
n=num
).plot(kind="bar", rot=0, ax=ax)
).plot(kind="bar", rot=0, ax=ax, color=colors)
ax.set_ylabel("Amount ($1k)")
ax.set_title(
f"{num} most sold stocks over last {past_transactions_months} months"
f" (upper bound) for {gov_type}"
)
# plt.gcf().axes[0].yaxis.get_major_formatter().set_scientific(False)
theme.style_primary_axis(ax)
@@ -608,6 +610,8 @@ def display_contracts(
ax.set_ylabel("Amount ($1k)")
ax.set_title(f"Sum of latest government contracts to {ticker}")
ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(4))
theme.style_primary_axis(ax)
if not external_axes:
@@ -775,7 +779,14 @@ def display_hist_contracts(
return
(ax,) = external_axes
ax.plot(np.arange(0, len(amounts)), amounts / 1000, "-*", lw=2, ms=15)
ax.plot(
np.arange(0, len(amounts)),
amounts / 1000,
marker=".",
markerfacecolor=theme.down_color,
lw=2,
ms=15,
)
ax.set_xlim([-0.5, len(amounts) - 0.5])
ax.set_xticks(np.arange(0, len(amounts)))
@@ -844,7 +855,8 @@ def display_top_lobbying(
return
(ax,) = external_axes
lobbying_by_ticker.head(num).plot(kind="bar", ax=ax)
colors = theme.get_colors()
lobbying_by_ticker.head(num).plot(kind="bar", ax=ax, color=colors)
ax.set_xlabel("Ticker")
ax.set_ylabel("Total Amount ($100k)")
ax.set_title(f"Corporate Lobbying Spent since {df_lobbying['Date'].min()}")

View File

@@ -103,7 +103,7 @@ def insider_activity(
* float(row["Shares Traded"].replace(",", "")),
axis=1,
)
ax.set_xlim(df_insider.index[0], stock.index[-1])
ax.set_xlim(right=stock.index[-1])
min_price, max_price = ax.get_ylim()
price_range = max_price - min_price

View File

@@ -296,8 +296,8 @@ def display_companies_per_sector_in_country(
df["Number of companies"] = df["Number of companies"].astype(int)
title = mktcap + " cap companies " if mktcap else "Companies "
title += f"in {country}"
title += " excl. exchanges" if exclude_exchanges else " incl. exchanges"
title += f"in {country}\n"
title += "excl. exchanges" if exclude_exchanges else " incl. exchanges"
if raw:
print_rich_table(df, headers=list(df.columns), show_index=True, title=title)
@@ -357,7 +357,7 @@ def display_companies_per_sector_in_country(
colors=colors,
wedgeprops={"linewidth": 0.5, "edgecolor": "white"},
labeldistance=1.05,
startangle=90,
startangle=45,
)
ax.set_title(title, fontsize=14)
@@ -439,8 +439,8 @@ def display_companies_per_industry_in_country(
df["Number of companies"] = df["Number of companies"].astype(int)
title = mktcap + " cap companies " if mktcap else "Companies "
title += f"in {country}"
title += " excl. exchanges" if exclude_exchanges else " incl. exchanges"
title += f"in {country}\n"
title += "excl. exchanges" if exclude_exchanges else " incl. exchanges"
if raw:
print_rich_table(df, headers=list(df.columns), show_index=True, title=title)
@@ -507,7 +507,7 @@ def display_companies_per_industry_in_country(
colors=colors,
wedgeprops={"linewidth": 0.5, "edgecolor": "white"},
labeldistance=1.05,
startangle=90,
startangle=45,
)
ax.set_title(title, fontsize=14)
@@ -587,8 +587,8 @@ def display_companies_per_industry_in_sector(
df["Number of companies"] = df["Number of companies"].astype(int)
title = mktcap + " cap companies " if mktcap else "Companies "
title += f"in {sector} sector"
title += " excl. exchanges" if exclude_exchanges else " incl. exchanges"
title += f"in {sector} sector\n"
title += "excl. exchanges" if exclude_exchanges else " incl. exchanges"
if raw:
print_rich_table(
@@ -660,7 +660,7 @@ def display_companies_per_industry_in_sector(
colors=colors,
wedgeprops={"linewidth": 0.5, "edgecolor": "white"},
labeldistance=1.05,
startangle=90,
startangle=45,
)
ax.set_title(title, fontsize=14)
@@ -739,8 +739,8 @@ def display_companies_per_country_in_sector(
df["Number of companies"] = df["Number of companies"].astype(int)
title = mktcap + " cap companies " if mktcap else "Companies "
title += f"in {sector} sector"
title += " excl. exchanges" if exclude_exchanges else " incl. exchanges"
title += f"in {sector} sector\n"
title += "excl. exchanges" if exclude_exchanges else " incl. exchanges"
if raw:
print_rich_table(df, headers=list(df.columns), show_index=True, title=title)
@@ -805,7 +805,7 @@ def display_companies_per_country_in_sector(
colors=colors,
wedgeprops={"linewidth": 0.5, "edgecolor": "white"},
labeldistance=1.05,
startangle=90,
startangle=45,
)
ax.set_title(title, fontsize=14)
@@ -884,8 +884,8 @@ def display_companies_per_country_in_industry(
df["Number of companies"] = df["Number of companies"].astype(int)
title = mktcap + " cap companies " if mktcap else "Companies "
title += f"per country in {industry} industry"
title += " excl. exchanges" if exclude_exchanges else " incl. exchanges"
title += f"per country in {industry} industry\n"
title += "excl. exchanges" if exclude_exchanges else " incl. exchanges"
if raw:
print_rich_table(df, headers=list(df.columns), show_index=True, title=title)
@@ -950,7 +950,7 @@ def display_companies_per_country_in_industry(
colors=colors,
wedgeprops={"linewidth": 0.5, "edgecolor": "white"},
labeldistance=1.05,
startangle=90,
startangle=45,
)
ax.set_title(title, fontsize=14)

View File

@@ -21,8 +21,8 @@ from numpy.core.fromnumeric import transpose
from plotly.subplots import make_subplots
from scipy import stats
from gamestonk_terminal.config_terminal import theme
from gamestonk_terminal import config_terminal as cfg
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import (
parse_known_args_and_warn,
plot_autoscale,
@@ -269,6 +269,7 @@ def display_candle(
add_trend: bool = False,
ma: Optional[Tuple[int, ...]] = None,
asset_type: str = "Stock",
external_axes: Optional[List[plt.Axes]] = None,
):
"""Shows candle plot of loaded ticker. [Source: Yahoo Finance, IEX Cloud or Alpha Vantage]
@@ -288,6 +289,10 @@ def display_candle(
Moving averages to add to the candle
asset_type_: str
String to include in title
external_axes : Optional[List[plt.Axes]], optional
External axes (2 axes are expected in the list), by default None
asset_type_: str
String to include in title
"""
if add_trend:
if (df_stock.index[1] - df_stock.index[0]).total_seconds() >= 86400:
@@ -299,45 +304,54 @@ def display_candle(
if add_trend:
if "OC_High_trend" in df_stock.columns:
ap0.append(
mpf.make_addplot(df_stock["OC_High_trend"], color="g"),
mpf.make_addplot(df_stock["OC_High_trend"], color=theme.up_color),
)
if "OC_Low_trend" in df_stock.columns:
ap0.append(
mpf.make_addplot(df_stock["OC_Low_trend"], color="b"),
mpf.make_addplot(df_stock["OC_Low_trend"], color=theme.down_color),
)
if gtff.USE_ION:
plt.ion()
candle_chart_kwargs = {
"type": "candle",
"style": theme.mpf_style,
"volume": True,
"addplot": ap0,
"xrotation": theme.xticks_rotation,
"scale_padding": {"left": 0.3, "right": 1, "top": 0.8, "bottom": 0.8},
"update_width_config": {
"candle_linewidth": 0.6,
"candle_width": 0.8,
"volume_linewidth": 0.8,
"volume_width": 0.8,
},
"warn_too_much_data": 10000,
}
kwargs = {"mav": ma} if ma else {}
fig, _ = mpf.plot(
df_stock,
type="candle",
style=cfg.theme.mpf_style,
volume=True,
addplot=ap0,
xrotation=10,
figratio=(10, 7),
figscale=1.10,
scale_padding={"left": 0.3, "right": 1.1, "top": 0.8, "bottom": 0.8},
figsize=(plot_autoscale()),
update_width_config=dict(
candle_linewidth=0.6,
candle_width=0.8,
volume_linewidth=0.8,
volume_width=0.8,
),
warn_too_much_data=10000,
returnfig=True,
**kwargs,
)
fig.suptitle(
f"{asset_type} {s_ticker}",
x=0.055,
y=0.965,
horizontalalignment="left",
)
if external_axes is None:
candle_chart_kwargs["returnfig"] = True
candle_chart_kwargs["figratio"] = (10, 7)
candle_chart_kwargs["figscale"] = 1.10
candle_chart_kwargs["figsize"] = plot_autoscale()
fig, _ = mpf.plot(df_stock, **candle_chart_kwargs, **kwargs)
fig.suptitle(
f"{asset_type} {s_ticker}",
x=0.055,
y=0.965,
horizontalalignment="left",
)
theme.visualize_output(force_tight_layout=False)
else:
if len(external_axes) != 1:
console.print("[red]Expected list of 1 axis items./n[/red]")
return
(ax1,) = external_axes
candle_chart_kwargs["ax"] = ax1
mpf.plot(df_stock, **candle_chart_kwargs)
else:
fig = make_subplots(
rows=2,

View File

@@ -54,6 +54,7 @@ xtick.major.size: 2
xtick.minor.size: 1
xtick.labelsize: small
xtick.alignment: center
xtick.major.width: 0.2
ytick.major.size: 2
ytick.minor.size: 1
@@ -63,6 +64,7 @@ ytick.left: False
ytick.labelleft: False
ytick.right: True
ytick.labelright: True
ytick.major.width: 0.2
grid.color: F5EFF3
grid.linewidth: 0.4

View File

@@ -6,6 +6,7 @@ import pytest
# IMPORTATION INTERNAL
from gamestonk_terminal.stocks import stocks_helper
from gamestonk_terminal import helper_funcs
@pytest.fixture(scope="module")
@@ -35,7 +36,7 @@ def test_quote():
)
def test_search(mocker, use_tab):
mocker.patch.object(
target=stocks_helper.gtff, attribute="USE_TABULATE_DF", new=use_tab
target=helper_funcs.gtff, attribute="USE_TABULATE_DF", new=use_tab
)
stocks_helper.search(query="pharma", amount=5)
@@ -73,8 +74,11 @@ def test_load(interval, recorder, source):
[True, False],
)
def test_display_candle(mocker, use_matplotlib):
mocker.patch.object(target=stocks_helper.gtff, attribute="USE_ION", new=False)
mocker.patch("matplotlib.pyplot.show")
# MOCK VISUALIZE_OUTPUT
mocker.patch(
target="gamestonk_terminal.helper_classes.TerminalStyle.visualize_output"
)
mocker.patch("plotly.basedatatypes.BaseFigure.show")
# LOAD DATA