Files
OpenBB/openbb_terminal/stocks/backtesting/bt_view.py
teh_coderer 602cfa2c8c [POC] Plotly Backend (#3941)
* Update portfolio_view.py

* mypy 1.0 test

* Update helpers.py

* black

* ruff

* Update thought_of_the_day.py

* isort

* Update portfolio_view.py

* Update business_insider_view.py

* funds,bt,ca,fa view updates

* economy,etf,futures view updates

* econometrics regression

* mypy

* reports notebooks, PlotlyTA class adjustments, crypto finbrain view

* crypto views updates

* crypto views done?, ba, econometrics, funds, reports

* update docstrings

* Update sec_view.py

* portfolio reports

* Update equity.ipynb

* ruff

* mypy + equity reports

* Update yahoo_finance_view.py

* Update economy_controller.py

* clean up returns

* mypy

* finished dashboards/reports templates

* update some tests

* Update common.py

* Update account_controller.py

* Update banner.txt

* update tests

* tests

* Update helpers_denomination.py

* tests

* tests

* finished tests, refactor add_histplot/add_corr_plot

* ruff

* Update backend.py

* mypy

* Update backend.py

* pywry linux libs for tests

* pywry version bump

* OpenBBFIgure import changes

* Update business_insider_view.py

* Update backend.py

* tests

* bump pywry version

* forecast plot_explainability converted to plotly

* Update helpers.py

* tests

* Update helpers.py

* tests

* Update helpers.py

* Update helpers.py

* default plotly annotation `showarrow` to false

* Update backend.py

* fixes

* Update poetry.lock

* docker

* Update openbb.dockerfile

* up

* Update style.css

* bump pywry version

* Update unit-test.yml

* Update unit-test.yml

* figure to image/pdf export

* requirements txt files, pywry version bump

* mypy, pylint

* cleanup

* Update covid_view.py

* Update helper_funcs.py

* Update backend.py

* updates

* Update backend.py

* Update backend.py

* bump pywry version

* Update poetry.lock

* Update trailmap.py

* update reports

* Update custom_indicators_plugin.py

* Update momentum_plugin.py

* equity report

* reqs files

* Update fred_view.py

* ruff

* Update po_view.py

* bump pywry version

* reqs txt files

* fix installer icons for mac

* test updated torch/darts/pyinstaller on mac

* Update dashboards_controller.py

* Update terminal.py

* Update terminal.spec

* Update terminal.spec

* test

* Update poetry.lock

* Report adjustments, improved dashboard subprocesses termination

* pylint

* Update plotly_helper.py

* Update plotly_helper.py

* pylint

* revert darts

* test

* test

* fix

* improve shap scatter plot

* Update helpers.py

* Update helpers.py

* cleanup

* Update runa_view.py

* more cleanupp

* fixes

* update tests

* Update intrinio_view.py

* Update intrinio_view.py

* Update intrinio_view.py

* reqs files, poetry lock

* Update backend.py

* test for new icon

* Update fmp_view.py

* updates

* Update plotly_helper.py

* Update businessinsider_view.py

* ruff

* pylint

* fix previous merge

* Update plotly_helper.py

* update reqs files

* Update directories.py

* fix alt covid

* fix display_candle data is None load being unreachable, get covid views ready for sdk server

* Update covid_view.py

* refactor cleanup

* plotly_ta adjustments

* terminal wide ignore print_table if export

* Update plotly_helper.py

* forgot to save a few

* added edge case fallback if figures are in queue before backend fail

* Update ta_class.py

* Rename env variable name required to return plotly json

* Resolve pydocstyle warnings

* Resolve linting errors

* revert forecasting/pyinstaller versions

* Update terminal.spec

* finished incoming fixedincome menu, improve get_ecb_yield_curve speed with multiprocessing

* update deps

* Update backend.py

* fix fred model/view from switch to DataFrame, improve ecb_model

* Update config_terminal.py

* Update backend.py

* black

* clean up

* Update fred_view.py

* Update ta_class.py

* Update ta_class.py

* Update plotly_helper.py

* sync fixedincome

* Update ecb_model.py

* convert excel to csv for faster processing

* cleanup

* remove excel files

* Update terminal_controller.py

* update tests

* adjustments/missed figs in export_data

* Update cryptocurrency_helpers.py

* fixes

* account for volume column with no volume when plotting indicators , images autogen POC

* Update plotly_helper.py

* Update etf_controller.py

* Update etf_controller.py

* margin arg for export_data func figures, some plot adjustments

* logic for numpy array for plotly helper `get_dateindex` function

* cleanup

* update

* req file updates

* views

* Update backend.py

* update tests

* Update test_load_none[btc-usd].yaml

* convert merged cpi chart

* linting

* updates

* clean

* tests

* Update openbb.dockerfile

* remove test docker in root

* Update CONTRIBUTING.md

---------

Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com>
Co-authored-by: Chavithra <chavithra@gmail.com>
2023-03-01 22:48:45 -05:00

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