diff options
author | Danglewood <85772166+deeleeramone@users.noreply.github.com> | 2024-07-03 11:59:10 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-03 11:59:10 -0700 |
commit | d9abc6b04490760bf8a4b99f6420838313c02d06 (patch) | |
tree | 2f7c710b6eebfc05667b7ef72178e77c16312eca | |
parent | de85d38edf6f66d7b76e9c52a0f0762bd5409c56 (diff) | |
parent | 0bd6cd0a1e7401fde7947c7f2bac5ec10a914cc6 (diff) |
Merge branch 'develop' into feature/openbb-store
38 files changed, 15943 insertions, 736 deletions
diff --git a/.github/scripts/noxfile.py b/.github/scripts/noxfile.py index 5bc4af2ce43..7e315394f11 100644 --- a/.github/scripts/noxfile.py +++ b/.github/scripts/noxfile.py @@ -13,7 +13,7 @@ CLI_DIR = ROOT_DIR / "cli" CLI_TESTS = CLI_DIR / "tests" -@nox.session(python=["3.9", "3.10", "3.11"]) +@nox.session(python=["3.9", "3.10", "3.11", "3.12"]) def unit_test_platform(session): """Run the test suite.""" session.install("poetry", "toml") @@ -31,7 +31,7 @@ def unit_test_platform(session): ) -@nox.session(python=["3.9", "3.10", "3.11"]) +@nox.session(python=["3.9", "3.10", "3.11", "3.12"]) def unit_test_cli(session): """Run the test suite.""" session.install("poetry", "toml") diff --git a/.github/workflows/test-unit-cli.yml b/.github/workflows/test-unit-cli.yml index fece7a9397a..fb0562c40ac 100644 --- a/.github/workflows/test-unit-cli.yml +++ b/.github/workflows/test-unit-cli.yml @@ -19,7 +19,7 @@ jobs: matrix: python_version: - ["3.9", "3.10", "3.11"] + ["3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/test-unit-platform.yml b/.github/workflows/test-unit-platform.yml index 0e8f9002091..a22cc673ea9 100644 --- a/.github/workflows/test-unit-platform.yml +++ b/.github/workflows/test-unit-platform.yml @@ -19,7 +19,7 @@ jobs: matrix: python_version: - ["3.9", "3.10", "3.11"] + ["3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/cli/pyproject.toml b/cli/pyproject.toml index 5aaf5c6c875..afdb8041189 100644 --- a/cli/pyproject.toml +++ b/cli/pyproject.toml @@ -14,7 +14,7 @@ documentation = "https://docs.openbb.co/terminal" openbb = 'openbb_cli.cli:main' [tool.poetry.dependencies] -python = "^3.9,<3.12" +python = "^3.9,<3.13" # OpenBB dependencies openbb = { version = "^4.2.3", extras = ["all"] } diff --git a/openbb_platform/core/openbb_core/app/static/utils/decorators.py b/openbb_platform/core/openbb_core/app/static/utils/decorators.py index d82f90d5a6b..0b2ea4f0ddd 100644 --- a/openbb_platform/core/openbb_core/app/static/utils/decorators.py +++ b/openbb_platform/core/openbb_core/app/static/utils/decorators.py @@ -57,7 +57,7 @@ def exception_handler(func: Callable[P, R]) -> Callable[P, R]: while tb.tb_next is not None: tb = tb.tb_next - if isinstance(e, ValidationError): + if isinstance(e, ValidationError) and "Data" not in e.title: error_list = [] validation_error = f"{e.error_count()} validations error(s)" for err in e.errors(include_url=False): diff --git a/openbb_platform/core/openbb_core/app/utils.py b/openbb_platform/core/openbb_core/app/utils.py index 452300c4334..366fcb6e447 100644 --- a/openbb_platform/core/openbb_core/app/utils.py +++ b/openbb_platform/core/openbb_core/app/utils.py @@ -21,12 +21,16 @@ def basemodel_to_df( ) -> pd.DataFrame: """Convert list of BaseModel to a Pandas DataFrame.""" if isinstance(data, list): - df = pd.DataFrame([d.model_dump() for d in data]) + df = pd.DataFrame( + [d.model_dump(exclude_none=True, exclude_unset=True) for d in data] + ) else: try: - df = pd.DataFrame(data.model_dump()) + df = pd.DataFrame(data.model_dump(exclude_none=True, exclude_unset=True)) except ValueError: - df = pd.DataFrame(data.model_dump(), index=["values"]) + df = pd.DataFrame( + data.model_dump(exclude_none=True, exclude_unset=True), index=["values"] + ) if "is_multiindex" in df.columns: col_names = ast.literal_eval(df.multiindex_names.unique()[0]) diff --git a/openbb_platform/core/openbb_core/provider/standard_models/futures_curve.py b/openbb_platform/core/openbb_core/provider/standard_models/futures_curve.py index 266de2e79e2..2afd1cf8a60 100644 --- a/openbb_platform/core/openbb_core/provider/standard_models/futures_curve.py +++ b/openbb_platform/core/openbb_core/provider/standard_models/futures_curve.py @@ -1,7 +1,7 @@ """Futures Curve Standard Model.""" from datetime import date as dateType -from typing import Optional +from typing import Optional, Union from pydantic import Field, field_validator @@ -17,14 +17,14 @@ class FuturesCurveQueryParams(QueryParams): """Futures Curve Query.""" symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "")) - date: Optional[dateType] = Field( + date: Optional[Union[dateType, str]] = Field( default=None, description=QUERY_DESCRIPTIONS.get("date", ""), ) @field_validator("symbol", mode="before", check_fields=False) @classmethod - def to_upper(cls, v: str) -> str: + def to_upper(cls, v): """Convert field to uppercase.""" return v.upper() @@ -32,7 +32,12 @@ class FuturesCurveQueryParams(QueryParams): class FuturesCurveData(Data): """Futures Curve Data.""" + date: Optional[dateType] = Field( + default=None, description=DATA_DESCRIPTIONS.get("date", "") + ) expiration: str = Field(description="Futures expiration month.") - price: Optional[float] = Field( - default=None, description=DATA_DESCRIPTIONS.get("close", "") + price: float = Field( + default=None, + description="The price of the futures contract.", + json_schema_extra={"x-unit_measurement": "currency"}, ) diff --git a/openbb_platform/core/tests/app/test_command_runner.py b/openbb_platform/core/tests/app/test_command_runner.py index 7c20059c100..ac792869aa2 100644 --- a/openbb_platform/core/tests/app/test_command_runner.py +++ b/openbb_platform/core/tests/app/test_command_runner.py @@ -254,14 +254,12 @@ def test_parameters_builder_build(mock_func, execution_context): } -@patch("openbb_core.app.command_runner.LoggingService") -def test_command_runner(_): +def test_command_runner(): """Test command runner.""" assert CommandRunner() -@patch("openbb_core.app.command_runner.LoggingService") -def test_command_runner_properties(mock_logging_service): +def test_command_runner_properties(): """Test properties.""" sys = SystemSettings() user = UserSettings() @@ -272,7 +270,6 @@ def test_command_runner_properties(mock_logging_service): assert runner.system_settings == sys assert runner.user_settings == user assert runner.command_map == cmd_map - assert mock_logging_service.called_once() @patch("openbb_core.app.command_runner.LoggingService") @@ -353,14 +350,14 @@ async def test_static_command_runner_execute_func( mock_chart.return_value = None result = await StaticCommandRunner._execute_func( - "mock/route", (1, 2, 3, 4), execution_context, mock_func, {} + "mock/route", (1, 2, 3, 4), execution_context, mock_func, {"chart": True} ) assert result.results == [1, 2, 3, 4] - assert mock_logging_service.called_once() - assert mock_parameters_builder_build.called_once() - assert mock_command.called_once() - assert mock_chart.called_once() + mock_logging_service.assert_called_once() + mock_parameters_builder_build.assert_called_once() + mock_command.assert_called_once() + mock_chart.assert_called_once() def test_static_command_runner_chart(): diff --git a/openbb_platform/dev_install.py b/openbb_platform/dev_install.py index d6b578f289a..364320e0d27 100644 --- a/openbb_platform/dev_install.py +++ b/openbb_platform/dev_install.py @@ -15,7 +15,7 @@ CLI_LOCK = CLI_PATH / "poetry.lock" LOCAL_DEPS = """ [tool.poetry.dependencies] -python = ">=3.9,<3.12" +python = ">=3.9,<3.13" openbb-devtools = { path = "./extensions/devtools", develop = true, markers = "python_version >= '3.10'" } openbb-core = { path = "./core", develop = true } diff --git a/openbb_platform/extensions/derivatives/integration/test_derivatives_api.py b/openbb_platform/extensions/derivatives/integration/test_derivatives_api.py index 97e9a3551eb..bc753851c3b 100644 --- a/openbb_platform/extensions/derivatives/integration/test_derivatives_api.py +++ b/openbb_platform/extensions/derivatives/integration/test_derivatives_api.py @@ -128,8 +128,20 @@ def test_derivatives_futures_historical(params, headers): @parametrize( "params", [ - ({"provider": "cboe", "symbol": "VX", "date": None}), - ({"provider": "yfinance", "symbol": "ES", "date": "2023-08-01"}), + ( + { + "provider": "yfinance", + "symbol": "ES", + "date": None, + } + ), + ( + { + "provider": "cboe", + "symbol": "VX_EOD", + "date": "2024-06-25", + } + ), ], ) @pytest.mark.integration @@ -139,7 +151,7 @@ def test_derivatives_futures_curve(params, headers): query_str = get_querystring(params, []) url = f"http://0.0.0.0:8000/api/v1/derivatives/futures/curve?{query_str}" - result = requests.get(url, headers=headers, timeout=60) + result = requests.get(url, headers=headers, timeout=10) assert isinstance(result, requests.Response) assert result.status_code == 200 diff --git a/openbb_platform/extensions/derivatives/integration/test_derivatives_python.py b/openbb_platform/extensions/derivatives/integration/test_derivatives_python.py index 4e04039bc7a..004e2727087 100644 --- a/openbb_platform/extensions/derivatives/integration/test_derivatives_python.py +++ b/openbb_platform/extensions/derivatives/integration/test_derivatives_python.py @@ -115,8 +115,8 @@ def test_derivatives_futures_historical(params, obb): @parametrize( "params", [ - ({"symbol": "VX", "provider": "cboe", "date": None}), - ({"provider": "yfinance", "symbol": "ES", "date": "2023-08-01"}), + ({"provider": "yfinance", "symbol": "ES", "date": None}), + ({"provider": "cboe", "symbol": "VX", "date": "2024-06-25"}), ], ) @pytest.mark.integration diff --git a/openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py b/openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py index c83b090e206..abe2fedba31 100644 --- a/openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py +++ b/openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py @@ -1,9 +1,9 @@ """Views for the Derivatives Extension.""" -from typing import Any, Dict, Tuple +from typing import TYPE_CHECKING, Any, Dict, Tuple -from openbb_charting.charts.price_historical import price_historical -from openbb_charting.core.openbb_figure import OpenBBFigure +if TYPE_CHECKING: + from openbb_charting.core.openbb_figure import OpenBBFigure class DerivativesViews: @@ -12,6 +12,215 @@ class DerivativesViews: @staticmethod def derivatives_futures_historical( # noqa: PLR0912 **kwargs, - ) -> Tuple[OpenBBFigure, Dict[str, Any]]: - """Get Derivatives Price Historical Chart.""" + ) -> Tuple["OpenBBFigure", Dict[str, Any]]: + """Get Derivatives Futures Historical Chart.""" + # pylint: disable=import-outside-toplevel + from openbb_charting.charts.price_historical import price_historical + return price_historical(**kwargs) + + @staticmethod + def derivatives_futures_curve( # noqa: PLR0912 + **kwargs, + ) -> Tuple["OpenBBFigure", Dict[str, Any]]: + """Futures curve chart. All parameters are optional, and are kwargs. + Parameters can be directly accessed from the function end point by + entering as a nested dictionary to the 'chart_params' key. + + From the API, `chart_params` must be passed as a JSON in the request body with `extra_params`. + + If using the chart post-request, the parameters are passed directly + as `key=value` pairs in the `charting.to_chart` or `charting.show` methods. + + Parameters + ---------- + data : Optional[Union[List[Data], DataFrame]] + Data for the chart. Required fields are: 'expiration' and 'price'. + Multiple dates will be plotted on the same chart. + If not supplied, the original OBBject.results will be used. + If a DataFrame is supplied, flat data is expected, without a set index. + title: Optional[str] + Title for the chart. If not supplied, a default title will be used. + colors: Optional[List[str]] + List of colors to use for the chart. If not supplied, the default colorway will be used. + Colors should be in hex format, or named Plotly colors. Invalid colors will raise a Plotly error. + layout_kwargs: Optional[Dict[str, Any]] + Additional layout parameters for the chart, passed directly to `figure.update_layout` before output. + See Plotly documentation for available options. + + Returns + ------- + Tuple[OpenBBFigure, Dict[str, Any]] + Tuple with the OpenBBFigure object, and the JSON-serialized content. + If using the API, only the JSON content will be returned. + + Examples + -------- + ```python + from openbb import obb + data = obb.derivatives.futures.curve(symbol="vx", provider="cboe", date=["2020-03-31", "2024-06-28"], chart=True) + data.show() + ``` + + Redraw the chart, from the same data, with a custom colorway and title: + + ```python + data.charting.to_chart(colors=["green", "red"], title="VIX Futures Curve - 2020 vs. 2024") + ``` + """ + # pylint: disable=import-outside-toplevel + from openbb_charting.core.chart_style import ChartStyle + from openbb_charting.core.openbb_figure import OpenBBFigure + from openbb_charting.styles.colors import LARGE_CYCLER + from openbb_core.app.model.abstract.error import OpenBBError + from openbb_core.provider.abstract.data import Data + from pandas import DataFrame, to_datetime + + data = kwargs.get("data", None) + symbol = kwargs.get("standard_params", {}).get("symbol", "") + df: DataFrame = DataFrame() + if data: + if isinstance(data, DataFrame) and not data.empty: # noqa: SIM108 + df = data + elif isinstance(data, (list, Data)): + df = DataFrame([d.model_dump(exclude_none=True, exclude_unset=True) for d in data]) # type: ignore + else: + pass + else: + df = DataFrame( + [ + d.model_dump(exclude_none=True, exclude_unset=True) # type: ignore + for d in kwargs["obbject_item"] + ] + if isinstance(kwargs.get("obbject_item"), list) + else kwargs["obbject_item"].model_dump(exclude_none=True, exclude_unset=True) # type: ignore + ) + + if df.empty: + raise OpenBBError("Error: No data to plot.") + + if "expiration" not in df.columns: + raise OpenBBError("Expiration field not found in the data.") + + if "price" not in df.columns: + raise ValueError("Price field not found in the data.") + + provider = kwargs.get("provider", "") + + df["expiration"] = to_datetime(df["expiration"], errors="ignore").dt.strftime( + "%b-%Y" + ) + + if ( + provider == "cboe" + and "date" in df.columns + and len(df["date"].unique()) > 1 + and "symbol" in df.columns + ): + df["expiration"] = df.symbol + + # Use a complete list of expirations to categorize the x-axis across all dates. + expirations = df["expiration"].unique().tolist() + + # Use the supplied colors, if any. + colors = kwargs.get("colors", []) + if not colors: + colors = LARGE_CYCLER + color_count = 0 + + figure = OpenBBFigure().create_subplots(shared_xaxes=True) + figure.update_layout(ChartStyle().plotly_template.get("layout", {})) + + def create_fig(figure, df, dates, color_count): + """Create a scatter for each date in the data.""" + for date in dates: + color = colors[color_count % len(colors)] + plot_df = ( + df[df["date"].astype(str) == date].copy() + if "date" in df.columns + else df.copy() + ) + plot_df |