Files
OpenBB/openbb_terminal/stocks/backtesting/bt_view.py
Pratyush Shukla 13283fbfce CI listing quick fix (#6002)
* BIGGGG LINTING

* fixing lints

* fixing lints

* black

* very ruff

* no export

* fix hedge_view again

* lints

* platform lints

* lints

* black

* black it @hjoaquim

* fix some more linting

---------

Co-authored-by: hjoaquim <h.joaquim@campus.fct.unl.pt>
2024-01-26 17:08:42 +00:00

317 lines
8.8 KiB
Python

"""bt view module"""
__docformat__ = "numpy"
import logging
import os
from datetime import datetime
from typing import Optional
import numpy as np
import pandas as pd
import yfinance as yf
from openbb_terminal import OpenBBFigure
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import export_data, is_intraday
from openbb_terminal.rich_config import console
from openbb_terminal.stocks.backtesting import bt_model
logger = logging.getLogger(__name__)
np.seterr(divide="ignore")
@log_start_end(log=logger)
def display_whatif_scenario(
symbol: str,
date_shares_acquired: Optional[datetime] = None,
num_shares_acquired: float = 1,
):
"""Display what if scenario
Parameters
----------
symbol: str
Ticker to check what if scenario
date_shares_acquired: str
Date at which the shares were acquired
num_shares_acquired: float
Number of shares acquired
"""
data = yf.download(symbol, progress=False, ignore_tz=True)
if not data.empty:
data = data["Adj Close"]
ipo_date = data.index[0]
last_date = data.index[-1]
if not date_shares_acquired:
date_shares_ac = ipo_date
console.print("IPO date selected by default.")
else:
date_shares_ac = date_shares_acquired
if date_shares_ac > last_date:
console.print("The date selected is in the future. Select a valid date.", "\n")
return
if date_shares_ac < ipo_date:
console.print(
f"{symbol} had not IPO at that date. Thus, changing the date to IPO on the {ipo_date.strftime('%Y-%m-%d')}",
"\n",
)
date_shares_ac = ipo_date
initial_shares_value = (
data[data.index > date_shares_ac].values[0] * num_shares_acquired
)
if (num_shares_acquired - int(num_shares_acquired)) > 0:
nshares = round(num_shares_acquired, 2)
else:
nshares = round(num_shares_acquired)
shares = "share"
these = "This"
if nshares > 1:
shares += "s"
these = "These"
console.print(
f"If you had acquired {nshares} {shares} of {symbol} on "
f"{date_shares_ac.strftime('%Y-%m-%d')} with a cost of {initial_shares_value:.2f}."
)
current_shares_value = (
data[data.index > date_shares_ac].values[-1] * num_shares_acquired
)
if current_shares_value > initial_shares_value:
pct = 100 * (
(current_shares_value - initial_shares_value) / initial_shares_value
)
console.print(
f"{these} would be worth {current_shares_value:.2f}. Which represents an increase of {pct:.2f}%.",
"\n",
)
else:
pct = 100 * (
(initial_shares_value - current_shares_value) / initial_shares_value
)
console.print(
f"{these} would be worth {current_shares_value:.2f}. Which represents an decrease of {pct:.2f}%.",
"\n",
)
@log_start_end(log=logger)
def display_simple_ema(
symbol: str,
data: pd.DataFrame,
ema_length: int = 20,
spy_bt: bool = True,
no_bench: bool = False,
export: str = "",
sheet_name: Optional[str] = None,
external_axes: bool = False,
):
"""Strategy where stock is bought when Price > EMA(l)
Parameters
----------
symbol : str
Stock ticker
data : pd.Dataframe
Dataframe of prices
ema_length : int
Length of ema window
spy_bt : bool
Boolean to add spy comparison
no_bench : bool
Boolean to not show buy and hold comparison
export : bool
Format to export backtest results
external_axes : bool, optional
Whether to return the figure object or not, by default False
"""
# TODO: Help Wanted!
# Implement support for backtesting on intraday data
if is_intraday(data):
console.print("Backtesting on intraday data is not yet supported.")
console.print("Submit a feature request to let us know that you need it here:")
return console.print("https://openbb.co/request-a-feature")
fig = OpenBBFigure(xaxis_title="Date").set_title(f"Equity for EMA({ema_length})")
res = bt_model.ema_strategy(symbol, data, ema_length, spy_bt, no_bench)
df_res = res._get_series(None).rebase() # pylint: disable=protected-access
for col in df_res.columns:
fig.add_scatter(
x=df_res.index,
y=df_res[col],
mode="lines",
name=col,
)
console.print(res.display(), "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"simple_ema",
res.stats,
sheet_name,
fig,
)
return fig.show(external=external_axes)
@log_start_end(log=logger)
def display_emacross(
symbol: str,
data: pd.DataFrame,
short_ema: int = 20,
long_ema: int = 50,
spy_bt: bool = True,
no_bench: bool = False,
shortable: bool = True,
export: str = "",
sheet_name: Optional[str] = None,
external_axes: bool = False,
): # pylint: disable=R0913
"""Strategy where we go long/short when EMA(short) is greater than/less than EMA(short)
Parameters
----------
symbol : str
Stock ticker
data : pd.Dataframe
Dataframe of prices
short_ema : int
Length of short ema window
long_ema : int
Length of long ema window
spy_bt : bool
Boolean to add spy comparison
no_bench : bool
Boolean to not show buy and hold comparison
shortable : bool
Boolean to allow for selling of the stock at cross
export : str
Format to export data
external_axes : bool, optional
Whether to return the figure object or not, by default False
"""
# TODO: Help Wanted!
# Implement support for backtesting on intraday data
if is_intraday(data):
console.print("Backtesting on intraday data is not yet supported.")
console.print("Submit a feature request to let us know that you need it here:")
return console.print("https://openbb.co/request-a-feature")
fig = OpenBBFigure(xaxis_title="Date").set_title(
f"Equity for EMA({short_ema})/EMA({long_ema})"
)
res = bt_model.emacross_strategy(
symbol, data, short_ema, long_ema, spy_bt, no_bench, shortable
)
df_res = res._get_series(None).rebase() # pylint: disable=protected-access
for col in df_res.columns:
fig.add_scatter(
x=df_res.index,
y=df_res[col],
mode="lines",
name=col,
)
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"emacross",
res.stats,
sheet_name,
fig,
)
return fig.show(external=external_axes)
# pylint:disable=too-many-arguments
@log_start_end(log=logger)
def display_rsi_strategy(
symbol: str,
data: pd.DataFrame,
periods: int = 14,
low_rsi: int = 30,
high_rsi: int = 70,
spy_bt: bool = True,
no_bench: bool = False,
shortable: bool = True,
export: str = "",
sheet_name: Optional[str] = None,
external_axes: bool = False,
):
"""Strategy that buys when the stock is less than a threshold and shorts when it exceeds a threshold.
Parameters
----------
symbol : str
Stock ticker
data : pd.Dataframe
Dataframe of prices
periods : int
Number of periods for RSI calculation
low_rsi : int
Low RSI value to buy
high_rsi : int
High RSI value to sell
spy_bt : bool
Boolean to add spy comparison
no_bench : bool
Boolean to not show buy and hold comparison
shortable : bool
Boolean to allow for selling of the stock at cross
export : str
Format to export backtest results
external_axes : bool, optional
Whether to return the figure object or not, by default False
"""
# TODO: Help Wanted!
# Implement support for backtesting on intraday data
if is_intraday(data):
console.print("Backtesting on intraday data is not yet supported.")
console.print("Submit a feature request to let us know that you need it here:")
return console.print("https://openbb.co/request-a-feature")
fig = OpenBBFigure(xaxis_title="Date").set_title(
f"Equity for RSI({periods}) between ({low_rsi}, {high_rsi})"
)
res = bt_model.rsi_strategy(
symbol, data, periods, low_rsi, high_rsi, spy_bt, no_bench, shortable
)
df_res = res._get_series(None).rebase() # pylint: disable=protected-access
for col in df_res.columns:
fig.add_scatter(
x=df_res.index,
y=df_res[col],
mode="lines",
name=col,
)
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"rsi_corss",
res.stats,
sheet_name,
fig,
)
return fig.show(external=external_axes)