CatPtain commited on
Commit
a2afe2f
·
verified ·
1 Parent(s): 3269d97

Upload 225 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. openbb_platform/extensions/README.md +3 -0
  2. openbb_platform/extensions/__init__.py +1 -0
  3. openbb_platform/extensions/commodity/README.md +13 -0
  4. openbb_platform/extensions/commodity/integration/test_commodity_api.py +112 -0
  5. openbb_platform/extensions/commodity/integration/test_commodity_python.py +100 -0
  6. openbb_platform/extensions/commodity/openbb_commodity/__init__.py +1 -0
  7. openbb_platform/extensions/commodity/openbb_commodity/commodity_router.py +80 -0
  8. openbb_platform/extensions/commodity/openbb_commodity/price/__init__.py +1 -0
  9. openbb_platform/extensions/commodity/openbb_commodity/price/price_router.py +33 -0
  10. openbb_platform/extensions/commodity/poetry.lock +0 -0
  11. openbb_platform/extensions/commodity/pyproject.toml +19 -0
  12. openbb_platform/extensions/crypto/README.md +13 -0
  13. openbb_platform/extensions/crypto/integration/test_crypto_api.py +126 -0
  14. openbb_platform/extensions/crypto/integration/test_crypto_python.py +117 -0
  15. openbb_platform/extensions/crypto/openbb_crypto/__init__.py +1 -0
  16. openbb_platform/extensions/crypto/openbb_crypto/crypto_router.py +35 -0
  17. openbb_platform/extensions/crypto/openbb_crypto/crypto_views.py +22 -0
  18. openbb_platform/extensions/crypto/openbb_crypto/price/__init__.py +1 -0
  19. openbb_platform/extensions/crypto/openbb_crypto/price/price_router.py +58 -0
  20. openbb_platform/extensions/crypto/openbb_crypto/py.typed +0 -0
  21. openbb_platform/extensions/crypto/poetry.lock +0 -0
  22. openbb_platform/extensions/crypto/pyproject.toml +22 -0
  23. openbb_platform/extensions/crypto/tests/.gitkeep +0 -0
  24. openbb_platform/extensions/currency/README.md +13 -0
  25. openbb_platform/extensions/currency/integration/test_currency_api.py +198 -0
  26. openbb_platform/extensions/currency/integration/test_currency_python.py +183 -0
  27. openbb_platform/extensions/currency/openbb_currency/__init__.py +1 -0
  28. openbb_platform/extensions/currency/openbb_currency/currency_router.py +99 -0
  29. openbb_platform/extensions/currency/openbb_currency/currency_views.py +22 -0
  30. openbb_platform/extensions/currency/openbb_currency/price/__init__.py +1 -0
  31. openbb_platform/extensions/currency/openbb_currency/price/price_router.py +53 -0
  32. openbb_platform/extensions/currency/openbb_currency/py.typed +0 -0
  33. openbb_platform/extensions/currency/poetry.lock +0 -0
  34. openbb_platform/extensions/currency/pyproject.toml +22 -0
  35. openbb_platform/extensions/currency/tests/.gitkeep +0 -0
  36. openbb_platform/extensions/derivatives/README.md +13 -0
  37. openbb_platform/extensions/derivatives/integration/test_derivatives_api.py +224 -0
  38. openbb_platform/extensions/derivatives/integration/test_derivatives_python.py +198 -0
  39. openbb_platform/extensions/derivatives/openbb_derivatives/__init__.py +1 -0
  40. openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_router.py +10 -0
  41. openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py +244 -0
  42. openbb_platform/extensions/derivatives/openbb_derivatives/futures/__init__.py +1 -0
  43. openbb_platform/extensions/derivatives/openbb_derivatives/futures/futures_router.py +97 -0
  44. openbb_platform/extensions/derivatives/openbb_derivatives/options/__init__.py +1 -0
  45. openbb_platform/extensions/derivatives/openbb_derivatives/options/options_router.py +74 -0
  46. openbb_platform/extensions/derivatives/poetry.lock +0 -0
  47. openbb_platform/extensions/derivatives/pyproject.toml +22 -0
  48. openbb_platform/extensions/derivatives/tests/.gitkeep +0 -0
  49. openbb_platform/extensions/devtools/README.md +22 -0
  50. openbb_platform/extensions/devtools/integration/.gitkeep +0 -0
openbb_platform/extensions/README.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Extensions
2
+
3
+ In this folder you can find the extensions that were created or are supported by OpenBB.
openbb_platform/extensions/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """OpenBB Platform Extensions."""
openbb_platform/extensions/commodity/README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Commodity Extension for OpenBB Platform
2
+
3
+ This extension provides a set of commands for commodity-related data.
4
+
5
+ ## Installation
6
+
7
+ To install the extension, run the following command in this folder:
8
+
9
+ ```bash
10
+ pip install openbb-commodity
11
+ ```
12
+
13
+ Documentation available [here](https://docs.openbb.co/platform/developer_guide/contributing).
openbb_platform/extensions/commodity/integration/test_commodity_api.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test Commodity API endpoints."""
2
+
3
+ import base64
4
+
5
+ import pytest
6
+ import requests
7
+ from openbb_core.env import Env
8
+ from openbb_core.provider.utils.helpers import get_querystring
9
+
10
+ # pylint: disable=redefined-outer-name
11
+
12
+
13
+ @pytest.fixture(scope="session")
14
+ def headers():
15
+ """Get the headers for the API request."""
16
+ userpass = f"{Env().API_USERNAME}:{Env().API_PASSWORD}"
17
+ userpass_bytes = userpass.encode("ascii")
18
+ base64_bytes = base64.b64encode(userpass_bytes)
19
+
20
+ return {"Authorization": f"Basic {base64_bytes.decode('ascii')}"}
21
+
22
+
23
+ @pytest.mark.parametrize(
24
+ "params",
25
+ [
26
+ (
27
+ {
28
+ "commodity": "all",
29
+ "start_date": None,
30
+ "end_date": None,
31
+ "frequency": None,
32
+ "transform": None,
33
+ "aggregation_method": None,
34
+ "provider": "fred",
35
+ }
36
+ ),
37
+ ],
38
+ )
39
+ @pytest.mark.integration
40
+ def test_commodity_price_spot(params, headers):
41
+ """Test the commodity spot prices endpoint."""
42
+ params = {p: v for p, v in params.items() if v}
43
+
44
+ query_str = get_querystring(params, [])
45
+ url = f"http://0.0.0.0:8000/api/v1/commodity/price/spot?{query_str}"
46
+ result = requests.get(url, headers=headers, timeout=10)
47
+ assert isinstance(result, requests.Response)
48
+ assert result.status_code == 200
49
+
50
+
51
+ @pytest.mark.parametrize(
52
+ "params",
53
+ [
54
+ (
55
+ {
56
+ "category": "balance_sheet",
57
+ "table": "stocks",
58
+ "start_date": None,
59
+ "end_date": None,
60
+ "provider": "eia",
61
+ "use_cache": True,
62
+ }
63
+ ),
64
+ (
65
+ {
66
+ "category": "weekly_estimates",
67
+ "table": "crude_production",
68
+ "start_date": "2020-01-01",
69
+ "end_date": "2023-12-31",
70
+ "provider": "eia",
71
+ "use_cache": True,
72
+ }
73
+ ),
74
+ ],
75
+ )
76
+ @pytest.mark.integration
77
+ def test_commodity_petroleum_status_report(params, headers):
78
+ """Test the Petroleum Status Report endpoint."""
79
+ params = {p: v for p, v in params.items() if v}
80
+
81
+ query_str = get_querystring(params, [])
82
+ url = f"http://0.0.0.0:8000/api/v1/commodity/petroleum_status_report?{query_str}"
83
+ result = requests.get(url, headers=headers, timeout=10)
84
+ assert isinstance(result, requests.Response)
85
+ assert result.status_code == 200
86
+
87
+
88
+ @pytest.mark.parametrize(
89
+ "params",
90
+ [
91
+ (
92
+ {
93
+ "table": "01",
94
+ "symbol": None,
95
+ "start_date": "2024-09-01",
96
+ "end_date": "2024-10-01",
97
+ "provider": "eia",
98
+ "frequency": "month",
99
+ }
100
+ ),
101
+ ],
102
+ )
103
+ @pytest.mark.integration
104
+ def test_commodity_short_term_energy_outlook(params, headers):
105
+ """Test the Short Term Energy Outlook endpoint."""
106
+ params = {p: v for p, v in params.items() if v}
107
+
108
+ query_str = get_querystring(params, [])
109
+ url = f"http://0.0.0.0:8000/api/v1/commodity/short_term_energy_outlook?{query_str}"
110
+ result = requests.get(url, headers=headers, timeout=10)
111
+ assert isinstance(result, requests.Response)
112
+ assert result.status_code == 200
openbb_platform/extensions/commodity/integration/test_commodity_python.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test Commodity extension."""
2
+
3
+ import pytest
4
+ from openbb_core.app.model.obbject import OBBject
5
+
6
+ # pylint: disable=redefined-outer-name
7
+
8
+
9
+ @pytest.fixture(scope="session")
10
+ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
11
+ """Fixture to setup obb."""
12
+ if pytestconfig.getoption("markexpr") != "not integration":
13
+ import openbb # pylint: disable=import-outside-toplevel
14
+
15
+ return openbb.obb
16
+
17
+
18
+ @pytest.mark.parametrize(
19
+ "params",
20
+ [
21
+ (
22
+ {
23
+ "commodity": "all",
24
+ "start_date": None,
25
+ "end_date": None,
26
+ "frequency": None,
27
+ "transform": None,
28
+ "aggregation_method": None,
29
+ "provider": "fred",
30
+ }
31
+ ),
32
+ ],
33
+ )
34
+ @pytest.mark.integration
35
+ def test_commodity_price_spot(params, obb):
36
+ """Test the commodity spot prices endpoint."""
37
+ params = {p: v for p, v in params.items() if v}
38
+
39
+ result = obb.commodity.price.spot(**params)
40
+ assert result
41
+ assert isinstance(result, OBBject)
42
+ assert len(result.results) > 0
43
+
44
+
45
+ @pytest.mark.parametrize(
46
+ "params",
47
+ [
48
+ (
49
+ {
50
+ "category": "balance_sheet",
51
+ "table": "stocks",
52
+ "start_date": None,
53
+ "end_date": None,
54
+ "provider": "eia",
55
+ "use_cache": True,
56
+ }
57
+ ),
58
+ (
59
+ {
60
+ "category": "weekly_estimates",
61
+ "table": "crude_production",
62
+ "start_date": "2020-01-01",
63
+ "end_date": "2023-12-31",
64
+ "provider": "eia",
65
+ "use_cache": True,
66
+ }
67
+ ),
68
+ ],
69
+ )
70
+ @pytest.mark.integration
71
+ def test_commodity_petroleum_status_report(params, obb):
72
+ """Test Commodity Petroleum Status Report endpoint."""
73
+ result = obb.commodity.petroleum_status_report(**params)
74
+ assert result
75
+ assert isinstance(result, OBBject)
76
+ assert len(result.results) > 0
77
+
78
+
79
+ @pytest.mark.parametrize(
80
+ "params",
81
+ [
82
+ (
83
+ {
84
+ "table": "01",
85
+ "symbol": None,
86
+ "start_date": "2024-09-01",
87
+ "end_date": "2024-10-01",
88
+ "provider": "eia",
89
+ "frequency": "month",
90
+ }
91
+ ),
92
+ ],
93
+ )
94
+ @pytest.mark.integration
95
+ def test_commodity_short_term_energy_outlook(params, obb):
96
+ """Test Commodity Short Term Energy Outlook endpoint."""
97
+ result = obb.commodity.short_term_energy_outlook(**params)
98
+ assert result
99
+ assert isinstance(result, OBBject)
100
+ assert len(result.results) > 0
openbb_platform/extensions/commodity/openbb_commodity/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """OpenBB Commodity Extension."""
openbb_platform/extensions/commodity/openbb_commodity/commodity_router.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The Commodity router."""
2
+
3
+ # pylint: disable=unused-argument,unused-import
4
+ # flake8: noqa: F401
5
+
6
+ # pylint: disable=unused-argument
7
+
8
+ from openbb_core.app.model.command_context import CommandContext
9
+ from openbb_core.app.model.example import APIEx
10
+ from openbb_core.app.model.obbject import OBBject
11
+ from openbb_core.app.provider_interface import (
12
+ ExtraParams,
13
+ ProviderChoices,
14
+ StandardParams,
15
+ )
16
+ from openbb_core.app.query import Query
17
+ from openbb_core.app.router import Router
18
+
19
+ from openbb_commodity.price.price_router import router as price_router
20
+
21
+ router = Router(prefix="", description="Commodity market data.")
22
+
23
+
24
+ router.include_router(price_router)
25
+
26
+
27
+ @router.command(
28
+ model="PetroleumStatusReport",
29
+ examples=[
30
+ APIEx(
31
+ description="Get the EIA's Weekly Petroleum Status Report.",
32
+ parameters={"provider": "eia"},
33
+ ),
34
+ APIEx(
35
+ description="Select the category of data, and filter for a specific table within the report.",
36
+ parameters={
37
+ "category": "weekly_estimates",
38
+ "table": "imports",
39
+ "provider": "eia",
40
+ },
41
+ ),
42
+ ],
43
+ )
44
+ async def petroleum_status_report(
45
+ cc: CommandContext,
46
+ provider_choices: ProviderChoices,
47
+ standard_params: StandardParams,
48
+ extra_params: ExtraParams,
49
+ ) -> OBBject:
50
+ """EIA Weekly Petroleum Status Report."""
51
+ return await OBBject.from_query(Query(**locals()))
52
+
53
+
54
+ @router.command(
55
+ model="ShortTermEnergyOutlook",
56
+ examples=[
57
+ APIEx(
58
+ description="Get the EIA's Short Term Energy Outlook.",
59
+ parameters={"provider": "eia"},
60
+ ),
61
+ APIEx(
62
+ description="Select the specific table of data from the STEO. Table 03d is World Crude Oil Production.",
63
+ parameters={
64
+ "table": "03d",
65
+ "provider": "eia",
66
+ },
67
+ ),
68
+ ],
69
+ )
70
+ async def short_term_energy_outlook(
71
+ cc: CommandContext,
72
+ provider_choices: ProviderChoices,
73
+ standard_params: StandardParams,
74
+ extra_params: ExtraParams,
75
+ ) -> OBBject:
76
+ """Monthly short term (18 month) projections using EIA's STEO model.
77
+
78
+ Source: www.eia.gov/steo/
79
+ """
80
+ return await OBBject.from_query(Query(**locals()))
openbb_platform/extensions/commodity/openbb_commodity/price/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Commodity Price."""
openbb_platform/extensions/commodity/openbb_commodity/price/price_router.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Price Router."""
2
+
3
+ # pylint: disable=unused-argument
4
+
5
+ from openbb_core.app.model.command_context import CommandContext
6
+ from openbb_core.app.model.example import APIEx
7
+ from openbb_core.app.model.obbject import OBBject
8
+ from openbb_core.app.provider_interface import (
9
+ ExtraParams,
10
+ ProviderChoices,
11
+ StandardParams,
12
+ )
13
+ from openbb_core.app.query import Query
14
+ from openbb_core.app.router import Router
15
+
16
+ router = Router(prefix="/price")
17
+
18
+
19
+ @router.command(
20
+ model="CommoditySpotPrices",
21
+ examples=[
22
+ APIEx(parameters={"provider": "fred"}),
23
+ APIEx(parameters={"provider": "fred", "commodity": "wti"}),
24
+ ],
25
+ )
26
+ async def spot(
27
+ cc: CommandContext,
28
+ provider_choices: ProviderChoices,
29
+ standard_params: StandardParams,
30
+ extra_params: ExtraParams,
31
+ ) -> OBBject:
32
+ """Commodity Spot Prices."""
33
+ return await OBBject.from_query(Query(**locals()))
openbb_platform/extensions/commodity/poetry.lock ADDED
The diff for this file is too large to render. See raw diff
 
openbb_platform/extensions/commodity/pyproject.toml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "openbb-commodity"
3
+ version = "1.3.1"
4
+ description = "Commodity extension for OpenBB"
5
+ authors = ["OpenBB Team <hello@openbb.co>"]
6
+ license = "AGPL-3.0-only"
7
+ readme = "README.md"
8
+ packages = [{ include = "openbb_commodity" }]
9
+
10
+ [tool.poetry.dependencies]
11
+ python = ">=3.9.21,<3.13"
12
+ openbb-core = "^1.4.6"
13
+
14
+ [build-system]
15
+ requires = ["poetry-core"]
16
+ build-backend = "poetry.core.masonry.api"
17
+
18
+ [tool.poetry.plugins."openbb_core_extension"]
19
+ commodity = "openbb_commodity.commodity_router:router"
openbb_platform/extensions/crypto/README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Crypto data extension for OpenBB Platform
2
+
3
+ This extension provides a set of commands for crypto data retrieval.
4
+
5
+ ## Installation
6
+
7
+ To install the extension, run the following command in this folder:
8
+
9
+ ```bash
10
+ pip install openbb-crypto
11
+ ```
12
+
13
+ Documentation available [here](https://docs.openbb.co/platform/developer_guide/contributing).
openbb_platform/extensions/crypto/integration/test_crypto_api.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test crypto API endpoints."""
2
+
3
+ import base64
4
+
5
+ import pytest
6
+ import requests
7
+ from extensions.tests.conftest import parametrize
8
+ from openbb_core.env import Env
9
+ from openbb_core.provider.utils.helpers import get_querystring
10
+
11
+ # pylint: disable=redefined-outer-name
12
+
13
+
14
+ @pytest.fixture(scope="session")
15
+ def headers():
16
+ """Get the headers for the API request."""
17
+ userpass = f"{Env().API_USERNAME}:{Env().API_PASSWORD}"
18
+ userpass_bytes = userpass.encode("ascii")
19
+ base64_bytes = base64.b64encode(userpass_bytes)
20
+
21
+ return {"Authorization": f"Basic {base64_bytes.decode('ascii')}"}
22
+
23
+
24
+ @parametrize(
25
+ "params",
26
+ [
27
+ ({"query": "asd"}),
28
+ ({"query": "btc", "provider": "fmp"}),
29
+ ],
30
+ )
31
+ @pytest.mark.integration
32
+ def test_crypto_search(params, headers):
33
+ """Test the crypto search endpoint."""
34
+ params = {p: v for p, v in params.items() if v}
35
+
36
+ query_str = get_querystring(params, [])
37
+ url = f"http://0.0.0.0:8000/api/v1/crypto/search?{query_str}"
38
+ result = requests.get(url, headers=headers, timeout=10)
39
+ assert isinstance(result, requests.Response)
40
+ assert result.status_code == 200
41
+
42
+
43
+ @parametrize(
44
+ "params",
45
+ [
46
+ (
47
+ {
48
+ "interval": "1d",
49
+ "provider": "fmp",
50
+ "symbol": "BTCUSD",
51
+ "start_date": "2023-01-01",
52
+ "end_date": "2023-01-02",
53
+ }
54
+ ),
55
+ (
56
+ {
57
+ "interval": "1h",
58
+ "provider": "fmp",
59
+ "symbol": "BTCUSD,ETHUSD",
60
+ "start_date": None,
61
+ "end_date": None,
62
+ }
63
+ ),
64
+ (
65
+ {
66
+ "interval": "1m",
67
+ "sort": "desc",
68
+ "limit": 49999,
69
+ "provider": "polygon",
70
+ "symbol": "BTCUSD",
71
+ "start_date": "2023-01-01",
72
+ "end_date": "2023-01-02",
73
+ }
74
+ ),
75
+ (
76
+ {
77
+ "interval": "1d",
78
+ "sort": "desc",
79
+ "limit": 49999,
80
+ "provider": "polygon",
81
+ "symbol": "BTCUSD",
82
+ "start_date": "2023-01-01",
83
+ "end_date": "2023-06-06",
84
+ }
85
+ ),
86
+ (
87
+ {
88
+ "interval": "1d",
89
+ "provider": "yfinance",
90
+ "symbol": "BTCUSD",
91
+ "start_date": "2023-01-01",
92
+ "end_date": "2023-01-04",
93
+ }
94
+ ),
95
+ (
96
+ {
97
+ "provider": "tiingo",
98
+ "interval": "1d",
99
+ "exchanges": None,
100
+ "symbol": "BTCUSD",
101
+ "start_date": "2023-01-01",
102
+ "end_date": "2023-06-06",
103
+ }
104
+ ),
105
+ (
106
+ {
107
+ "provider": "tiingo",
108
+ "interval": "1h",
109
+ "exchanges": ["POLONIEX", "GDAX"],
110
+ "symbol": "BTCUSD",
111
+ "start_date": "2023-01-01",
112
+ "end_date": "2023-01-02",
113
+ }
114
+ ),
115
+ ],
116
+ )
117
+ @pytest.mark.integration
118
+ def test_crypto_price_historical(params, headers):
119
+ """Test the crypto historical price endpoint."""
120
+ params = {p: v for p, v in params.items() if v}
121
+
122
+ query_str = get_querystring(params, [])
123
+ url = f"http://0.0.0.0:8000/api/v1/crypto/price/historical?{query_str}"
124
+ result = requests.get(url, headers=headers, timeout=10)
125
+ assert isinstance(result, requests.Response)
126
+ assert result.status_code == 200
openbb_platform/extensions/crypto/integration/test_crypto_python.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test crypto extension."""
2
+
3
+ import pytest
4
+ from extensions.tests.conftest import parametrize
5
+ from openbb_core.app.model.obbject import OBBject
6
+
7
+ # pylint: disable=redefined-outer-name
8
+
9
+
10
+ @pytest.fixture(scope="session")
11
+ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
12
+ """Fixture to setup obb."""
13
+ if pytestconfig.getoption("markexpr") != "not integration":
14
+ import openbb # pylint: disable=import-outside-toplevel
15
+
16
+ return openbb.obb
17
+
18
+
19
+ @parametrize(
20
+ "params",
21
+ [
22
+ ({"query": "asd"}),
23
+ ({"query": "btc", "provider": "fmp"}),
24
+ ],
25
+ )
26
+ @pytest.mark.integration
27
+ def test_crypto_search(params, obb):
28
+ """Test the crypto search endpoint."""
29
+ params = {p: v for p, v in params.items() if v}
30
+
31
+ result = obb.crypto.search(**params)
32
+ assert result
33
+ assert isinstance(result, OBBject)
34
+ assert len(result.results) > 0
35
+
36
+
37
+ @parametrize(
38
+ "params",
39
+ [
40
+ (
41
+ {
42
+ "interval": "1d",
43
+ "provider": "fmp",
44
+ "symbol": "BTCUSD",
45
+ "start_date": "2023-01-01",
46
+ "end_date": "2023-01-02",
47
+ }
48
+ ),
49
+ (
50
+ {
51
+ "interval": "1h",
52
+ "provider": "fmp",
53
+ "symbol": "BTCUSD,ETHUSD",
54
+ "start_date": None,
55
+ "end_date": None,
56
+ }
57
+ ),
58
+ (
59
+ {
60
+ "interval": "1m",
61
+ "sort": "desc",
62
+ "limit": 49999,
63
+ "provider": "polygon",
64
+ "symbol": "BTCUSD",
65
+ "start_date": "2023-01-01",
66
+ "end_date": "2023-01-02",
67
+ }
68
+ ),
69
+ (
70
+ {
71
+ "interval": "1d",
72
+ "sort": "desc",
73
+ "limit": 49999,
74
+ "provider": "polygon",
75
+ "symbol": "BTCUSD",
76
+ "start_date": "2023-01-01",
77
+ "end_date": "2023-06-06",
78
+ }
79
+ ),
80
+ (
81
+ {
82
+ "interval": "1d",
83
+ "provider": "yfinance",
84
+ "symbol": "BTCUSD",
85
+ "start_date": "2023-01-01",
86
+ "end_date": "2023-01-04",
87
+ }
88
+ ),
89
+ (
90
+ {
91
+ "provider": "tiingo",
92
+ "interval": "1d",
93
+ "exchanges": None,
94
+ "symbol": "BTCUSD",
95
+ "start_date": "2023-01-01",
96
+ "end_date": "2023-06-06",
97
+ }
98
+ ),
99
+ (
100
+ {
101
+ "provider": "tiingo",
102
+ "interval": "1h",
103
+ "exchanges": ["POLONIEX", "GDAX"],
104
+ "symbol": "BTCUSD",
105
+ "start_date": "2023-01-01",
106
+ "end_date": "2023-01-02",
107
+ }
108
+ ),
109
+ ],
110
+ )
111
+ @pytest.mark.integration
112
+ def test_crypto_price_historical(params, obb):
113
+ """Test crypto price historical."""
114
+ result = obb.crypto.price.historical(**params)
115
+ assert result
116
+ assert isinstance(result, OBBject)
117
+ assert len(result.results) > 0
openbb_platform/extensions/crypto/openbb_crypto/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """OpenBB Crypto Extension."""
openbb_platform/extensions/crypto/openbb_crypto/crypto_router.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Crypto Router."""
2
+
3
+ from openbb_core.app.model.command_context import CommandContext
4
+ from openbb_core.app.model.example import APIEx
5
+ from openbb_core.app.model.obbject import OBBject
6
+ from openbb_core.app.provider_interface import (
7
+ ExtraParams,
8
+ ProviderChoices,
9
+ StandardParams,
10
+ )
11
+ from openbb_core.app.query import Query
12
+ from openbb_core.app.router import Router
13
+
14
+ from openbb_crypto.price.price_router import router as price_router
15
+
16
+ router = Router(prefix="", description="Cryptocurrency market data.")
17
+ router.include_router(price_router)
18
+
19
+
20
+ # pylint: disable=unused-argument
21
+ @router.command(
22
+ model="CryptoSearch",
23
+ examples=[
24
+ APIEx(parameters={"provider": "fmp"}),
25
+ APIEx(parameters={"query": "BTCUSD", "provider": "fmp"}),
26
+ ],
27
+ )
28
+ async def search(
29
+ cc: CommandContext,
30
+ provider_choices: ProviderChoices,
31
+ standard_params: StandardParams,
32
+ extra_params: ExtraParams,
33
+ ) -> OBBject:
34
+ """Search available cryptocurrency pairs within a provider."""
35
+ return await OBBject.from_query(Query(**locals()))
openbb_platform/extensions/crypto/openbb_crypto/crypto_views.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Views for the crypto Extension."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Dict, Tuple
4
+
5
+ if TYPE_CHECKING:
6
+ from openbb_charting.core.openbb_figure import (
7
+ OpenBBFigure,
8
+ )
9
+
10
+
11
+ class CryptoViews:
12
+ """Crypto Views."""
13
+
14
+ @staticmethod
15
+ def crypto_price_historical( # noqa: PLR0912
16
+ **kwargs,
17
+ ) -> Tuple["OpenBBFigure", Dict[str, Any]]:
18
+ """Crypto Price Historical Chart."""
19
+ # pylint: disable=import-outside-toplevel
20
+ from openbb_charting.charts.price_historical import price_historical
21
+
22
+ return price_historical(**kwargs)
openbb_platform/extensions/crypto/openbb_crypto/price/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """OpenBB Crypto Price Router."""
openbb_platform/extensions/crypto/openbb_crypto/price/price_router.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pylint: disable=W0613:unused-argument
2
+ """Crypto Price Router."""
3
+
4
+ from openbb_core.app.model.command_context import CommandContext
5
+ from openbb_core.app.model.example import APIEx
6
+ from openbb_core.app.model.obbject import OBBject
7
+ from openbb_core.app.provider_interface import (
8
+ ExtraParams,
9
+ ProviderChoices,
10
+ StandardParams,
11
+ )
12
+ from openbb_core.app.query import Query
13
+ from openbb_core.app.router import Router
14
+
15
+ router = Router(prefix="/price")
16
+
17
+
18
+ # pylint: disable=unused-argument,line-too-long
19
+ @router.command(
20
+ model="CryptoHistorical",
21
+ examples=[
22
+ APIEx(parameters={"symbol": "BTCUSD", "provider": "fmp"}),
23
+ APIEx(
24
+ parameters={
25
+ "symbol": "BTCUSD",
26
+ "start_date": "2024-01-01",
27
+ "end_date": "2024-01-31",
28
+ "provider": "fmp",
29
+ },
30
+ ),
31
+ APIEx(
32
+ parameters={
33
+ "symbol": "BTCUSD,ETHUSD",
34
+ "start_date": "2024-01-01",
35
+ "end_date": "2024-01-31",
36
+ "provider": "polygon",
37
+ },
38
+ ),
39
+ APIEx(
40
+ description="Get monthly historical prices from Yahoo Finance for Ethereum.",
41
+ parameters={
42
+ "symbol": "ETH-USD",
43
+ "interval": "1m",
44
+ "start_date": "2024-01-01",
45
+ "end_date": "2024-12-31",
46
+ "provider": "yfinance",
47
+ },
48
+ ),
49
+ ],
50
+ )
51
+ async def historical(
52
+ cc: CommandContext,
53
+ provider_choices: ProviderChoices,
54
+ standard_params: StandardParams,
55
+ extra_params: ExtraParams,
56
+ ) -> OBBject:
57
+ """Get historical price data for cryptocurrency pair(s) within a provider."""
58
+ return await OBBject.from_query(Query(**locals()))
openbb_platform/extensions/crypto/openbb_crypto/py.typed ADDED
File without changes
openbb_platform/extensions/crypto/poetry.lock ADDED
The diff for this file is too large to render. See raw diff
 
openbb_platform/extensions/crypto/pyproject.toml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "openbb-crypto"
3
+ version = "1.4.1"
4
+ description = "Crypto extension for OpenBB"
5
+ authors = ["OpenBB Team <hello@openbb.co>"]
6
+ license = "AGPL-3.0-only"
7
+ readme = "README.md"
8
+ packages = [{ include = "openbb_crypto" }]
9
+
10
+ [tool.poetry.dependencies]
11
+ python = ">=3.9.21,<3.13"
12
+ openbb-core = "^1.4.6"
13
+
14
+ [build-system]
15
+ requires = ["poetry-core"]
16
+ build-backend = "poetry.core.masonry.api"
17
+
18
+ [tool.poetry.plugins."openbb_core_extension"]
19
+ crypto = "openbb_crypto.crypto_router:router"
20
+
21
+ [tool.poetry.plugins."openbb_charting_extension"]
22
+ crypto = "openbb_crypto.crypto_views:CryptoViews"
openbb_platform/extensions/crypto/tests/.gitkeep ADDED
File without changes
openbb_platform/extensions/currency/README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenBB Currency Extension
2
+
3
+ This extension provides currency exchange related data for the OpenBB Platform.
4
+
5
+ ## Installation
6
+
7
+ To install the extension, run the following command in this folder:
8
+
9
+ ```bash
10
+ pip install openbb-currency
11
+ ```
12
+
13
+ Documentation available [here](https://docs.openbb.co/platform/developer_guide/contributing).
openbb_platform/extensions/currency/integration/test_currency_api.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test currency API endpoints."""
2
+
3
+ import base64
4
+
5
+ import pytest
6
+ import requests
7
+ from extensions.tests.conftest import parametrize
8
+ from openbb_core.env import Env
9
+ from openbb_core.provider.utils.helpers import get_querystring
10
+
11
+ # pylint: disable=redefined-outer-name
12
+
13
+
14
+ @pytest.fixture(scope="session")
15
+ def headers():
16
+ """Get the headers for the API request."""
17
+ userpass = f"{Env().API_USERNAME}:{Env().API_PASSWORD}"
18
+ userpass_bytes = userpass.encode("ascii")
19
+ base64_bytes = base64.b64encode(userpass_bytes)
20
+
21
+ return {"Authorization": f"Basic {base64_bytes.decode('ascii')}"}
22
+
23
+
24
+ @parametrize(
25
+ "params",
26
+ [
27
+ (
28
+ {
29
+ "provider": "polygon",
30
+ "query": "eur",
31
+ }
32
+ ),
33
+ (
34
+ {
35
+ "provider": "fmp",
36
+ "query": "eur",
37
+ }
38
+ ),
39
+ (
40
+ {
41
+ "provider": "intrinio",
42
+ "query": "eur",
43
+ }
44
+ ),
45
+ ],
46
+ )
47
+ @pytest.mark.integration
48
+ def test_currency_search(params, headers):
49
+ """Test the currency search endpoint."""
50
+ params = {p: v for p, v in params.items() if v}
51
+
52
+ query_str = get_querystring(params, [])
53
+ url = f"http://0.0.0.0:8000/api/v1/currency/search?{query_str}"
54
+ result = requests.get(url, headers=headers, timeout=10)
55
+ assert isinstance(result, requests.Response)
56
+ assert result.status_code == 200
57
+
58
+
59
+ @parametrize(
60
+ "params",
61
+ [
62
+ (
63
+ {
64
+ "symbol": "EURUSD",
65
+ "interval": "1d",
66
+ "start_date": "2023-01-01",
67
+ "end_date": "2023-06-06",
68
+ "provider": "fmp",
69
+ }
70
+ ),
71
+ (
72
+ {
73
+ "interval": "1h",
74
+ "provider": "fmp",
75
+ "symbol": "EURUSD,USDJPY",
76
+ "start_date": None,
77
+ "end_date": None,
78
+ }
79
+ ),
80
+ (
81
+ {
82
+ "interval": "1m",
83
+ "sort": "desc",
84
+ "limit": 49999,
85
+ "provider": "polygon",
86
+ "symbol": "EURUSD",
87
+ "start_date": "2023-01-01",
88
+ "end_date": "2023-01-10",
89
+ }
90
+ ),
91
+ (
92
+ {
93
+ "interval": "1d",
94
+ "sort": "desc",
95
+ "limit": 49999,
96
+ "provider": "polygon",
97
+ "symbol": "EURUSD",
98
+ "start_date": "2023-01-01",
99
+ "end_date": "2023-06-06",
100
+ }
101
+ ),
102
+ (
103
+ {
104
+ "interval": "1d",
105
+ "provider": "yfinance",
106
+ "symbol": "EURUSD",
107
+ "start_date": "2023-01-01",
108
+ "end_date": "2023-01-10",
109
+ }
110
+ ),
111
+ (
112
+ {
113
+ "interval": "1m",
114
+ "provider": "yfinance",
115
+ "symbol": "EURUSD",
116
+ "start_date": None,
117
+ "end_date": None,
118
+ }
119
+ ),
120
+ (
121
+ {
122
+ "interval": "1h",
123
+ "provider": "tiingo",
124
+ "symbol": "EURUSD",
125
+ "start_date": "2023-05-21",
126
+ "end_date": "2023-06-06",
127
+ }
128
+ ),
129
+ (
130
+ {
131
+ "interval": "1d",
132
+ "provider": "tiingo",
133
+ "symbol": "EURUSD",
134
+ "start_date": "2023-05-21",
135
+ "end_date": "2023-06-06",
136
+ }
137
+ ),
138
+ ],
139
+ )
140
+ @pytest.mark.integration
141
+ def test_currency_price_historical(params, headers):
142
+ """Test the currency historical price endpoint."""
143
+ params = {p: v for p, v in params.items() if v}
144
+
145
+ query_str = get_querystring(params, [])
146
+ url = f"http://0.0.0.0:8000/api/v1/currency/price/historical?{query_str}"
147
+ result = requests.get(url, headers=headers, timeout=10)
148
+ assert isinstance(result, requests.Response)
149
+ assert result.status_code == 200
150
+
151
+
152
+ @parametrize(
153
+ "params",
154
+ [({"provider": "ecb"})],
155
+ )
156
+ @pytest.mark.integration
157
+ def test_currency_reference_rates(params, headers):
158
+ """Test the currency reference rates endpoint."""
159
+ params = {p: v for p, v in params.items() if v}
160
+
161
+ query_str = get_querystring(params, [])
162
+ url = f"http://0.0.0.0:8000/api/v1/currency/reference_rates?{query_str}"
163
+ result = requests.get(url, headers=headers, timeout=10)
164
+ assert isinstance(result, requests.Response)
165
+ assert result.status_code == 200
166
+
167
+
168
+ @parametrize(
169
+ "params",
170
+ [
171
+ (
172
+ {
173
+ "provider": "fmp",
174
+ "base": "USD,XAU",
175
+ "counter_currencies": "EUR,JPY,GBP",
176
+ "quote_type": "indirect",
177
+ }
178
+ ),
179
+ (
180
+ {
181
+ "provider": "polygon",
182
+ "base": "USD,XAU",
183
+ "counter_currencies": "EUR,JPY,GBP",
184
+ "quote_type": "indirect",
185
+ }
186
+ ),
187
+ ],
188
+ )
189
+ @pytest.mark.integration
190
+ def test_currency_snapshots(params, headers):
191
+ """Test the currency snapshots endpoint."""
192
+ params = {p: v for p, v in params.items() if v}
193
+
194
+ query_str = get_querystring(params, [])
195
+ url = f"http://0.0.0.0:8000/api/v1/currency/snapshots?{query_str}"
196
+ result = requests.get(url, headers=headers, timeout=10)
197
+ assert isinstance(result, requests.Response)
198
+ assert result.status_code == 200
openbb_platform/extensions/currency/integration/test_currency_python.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test currency extension."""
2
+
3
+ import pytest
4
+ from extensions.tests.conftest import parametrize
5
+ from openbb_core.app.model.obbject import OBBject
6
+
7
+ # pylint: disable=redefined-outer-name
8
+ # pylint: disable=inconsistent-return-statements
9
+
10
+
11
+ @pytest.fixture(scope="session")
12
+ def obb(pytestconfig):
13
+ """Fixture to setup obb."""
14
+
15
+ if pytestconfig.getoption("markexpr") != "not integration":
16
+ import openbb # pylint: disable=import-outside-toplevel
17
+
18
+ return openbb.obb
19
+
20
+
21
+ @parametrize(
22
+ "params",
23
+ [
24
+ (
25
+ {
26
+ "provider": "polygon",
27
+ "query": "eur",
28
+ }
29
+ ),
30
+ (
31
+ {
32
+ "provider": "fmp",
33
+ "query": "eur",
34
+ }
35
+ ),
36
+ (
37
+ {
38
+ "provider": "intrinio",
39
+ "query": "eur",
40
+ }
41
+ ),
42
+ ],
43
+ )
44
+ @pytest.mark.integration
45
+ def test_currency_search(params, obb):
46
+ """Test the currency search endpoint."""
47
+ result = obb.currency.search(**params)
48
+ assert result
49
+ assert isinstance(result, OBBject)
50
+ assert len(result.results) > 0
51
+
52
+
53
+ @parametrize(
54
+ "params",
55
+ [
56
+ (
57
+ {
58
+ "symbol": "EURUSD",
59
+ "interval": "1d",
60
+ "start_date": "2023-01-01",
61
+ "end_date": "2023-06-06",
62
+ "provider": "fmp",
63
+ }
64
+ ),
65
+ (
66
+ {
67
+ "interval": "1h",
68
+ "provider": "fmp",
69
+ "symbol": "EURUSD,USDJPY",
70
+ "start_date": None,
71
+ "end_date": None,
72
+ }
73
+ ),
74
+ (
75
+ {
76
+ "interval": "1m",
77
+ "sort": "desc",
78
+ "limit": 49999,
79
+ "provider": "polygon",
80
+ "symbol": "EURUSD",
81
+ "start_date": "2023-01-01",
82
+ "end_date": "2023-01-10",
83
+ }
84
+ ),
85
+ (
86
+ {
87
+ "interval": "1d",
88
+ "sort": "desc",
89
+ "limit": 49999,
90
+ "provider": "polygon",
91
+ "symbol": "EURUSD",
92
+ "start_date": "2023-01-01",
93
+ "end_date": "2023-06-06",
94
+ }
95
+ ),
96
+ (
97
+ {
98
+ "interval": "1d",
99
+ "provider": "yfinance",
100
+ "symbol": "EURUSD",
101
+ "start_date": "2023-01-01",
102
+ "end_date": "2023-01-10",
103
+ }
104
+ ),
105
+ (
106
+ {
107
+ "interval": "1m",
108
+ "provider": "yfinance",
109
+ "symbol": "EURUSD",
110
+ "start_date": None,
111
+ "end_date": None,
112
+ }
113
+ ),
114
+ (
115
+ {
116
+ "interval": "1h",
117
+ "provider": "tiingo",
118
+ "symbol": "EURUSD",
119
+ "start_date": "2023-05-21",
120
+ "end_date": "2023-06-06",
121
+ }
122
+ ),
123
+ (
124
+ {
125
+ "interval": "1d",
126
+ "provider": "tiingo",
127
+ "symbol": "EURUSD",
128
+ "start_date": "2023-05-21",
129
+ "end_date": "2023-06-06",
130
+ }
131
+ ),
132
+ ],
133
+ )
134
+ @pytest.mark.integration
135
+ def test_currency_price_historical(params, obb):
136
+ """Test the currency historical price endpoint."""
137
+ result = obb.currency.price.historical(**params)
138
+ assert result
139
+ assert isinstance(result, OBBject)
140
+ assert len(result.results) > 0
141
+
142
+
143
+ @parametrize(
144
+ "params",
145
+ [({"provider": "ecb"})],
146
+ )
147
+ @pytest.mark.integration
148
+ def test_currency_reference_rates(params, obb):
149
+ """Test the currency reference rates endpoint."""
150
+ result = obb.currency.reference_rates(**params)
151
+ assert result
152
+ assert isinstance(result, OBBject)
153
+ assert len(result.model_dump()["results"].items()) > 0
154
+
155
+
156
+ @parametrize(
157
+ "params",
158
+ [
159
+ (
160
+ {
161
+ "provider": "fmp",
162
+ "base": "USD,XAU",
163
+ "counter_currencies": "EUR,JPY,GBP",
164
+ "quote_type": "indirect",
165
+ }
166
+ ),
167
+ (
168
+ {
169
+ "provider": "polygon",
170
+ "base": "USD,XAU",
171
+ "counter_currencies": "EUR,JPY,GBP",
172
+ "quote_type": "indirect",
173
+ }
174
+ ),
175
+ ],
176
+ )
177
+ @pytest.mark.integration
178
+ def test_currency_snapshots(params, obb):
179
+ """Test the currency snapshots endpoint."""
180
+ result = obb.currency.snapshots(**params)
181
+ assert result
182
+ assert isinstance(result, OBBject)
183
+ assert len(result.results) > 0
openbb_platform/extensions/currency/openbb_currency/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """The Currency router init."""
openbb_platform/extensions/currency/openbb_currency/currency_router.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The Currency router."""
2
+
3
+ from openbb_core.app.model.command_context import CommandContext
4
+ from openbb_core.app.model.example import APIEx
5
+ from openbb_core.app.model.obbject import OBBject
6
+ from openbb_core.app.provider_interface import (
7
+ ExtraParams,
8
+ ProviderChoices,
9
+ StandardParams,
10
+ )
11
+ from openbb_core.app.query import Query
12
+ from openbb_core.app.router import Router
13
+
14
+ from openbb_currency.price.price_router import router as price_router
15
+
16
+ router = Router(prefix="", description="Foreign exchange (FX) market data.")
17
+ router.include_router(price_router)
18
+
19
+
20
+ # pylint: disable=unused-argument
21
+ @router.command(
22
+ model="CurrencyPairs",
23
+ examples=[
24
+ APIEx(parameters={"provider": "fmp"}),
25
+ APIEx(
26
+ description="Search for 'EUR' currency pair using 'intrinio' as provider.",
27
+ parameters={"provider": "intrinio", "query": "EUR"},
28
+ ),
29
+ APIEx(
30
+ description="Search for terms using 'polygon' as provider.",
31
+ parameters={"provider": "polygon", "query": "EUR"},
32
+ ),
33
+ ],
34
+ )
35
+ async def search(
36
+ cc: CommandContext,
37
+ provider_choices: ProviderChoices,
38
+ standard_params: StandardParams,
39
+ extra_params: ExtraParams,
40
+ ) -> OBBject:
41
+ """Currency Search.
42
+
43
+ Search available currency pairs.
44
+ Currency pairs are the national currencies from two countries coupled for trading on
45
+ the foreign exchange (FX) marketplace.
46
+ Both currencies will have exchange rates on which the trade will have its position basis.
47
+ All trading within the forex market, whether selling, buying, or trading, will take place through currency pairs.
48
+ (ref: Investopedia)
49
+ Major currency pairs include pairs such as EUR/USD, USD/JPY, GBP/USD, etc.
50
+ """
51
+ return await OBBject.from_query(Query(**locals()))
52
+
53
+
54
+ @router.command(
55
+ model="CurrencyReferenceRates",
56
+ examples=[APIEx(parameters={"provider": "ecb"})],
57
+ )
58
+ async def reference_rates(
59
+ cc: CommandContext,
60
+ provider_choices: ProviderChoices,
61
+ standard_params: StandardParams,
62
+ extra_params: ExtraParams,
63
+ ) -> OBBject:
64
+ """Get current, official, currency reference rates.
65
+
66
+ Foreign exchange reference rates are the exchange rates set by a major financial institution or regulatory body,
67
+ serving as a benchmark for the value of currencies around the world.
68
+ These rates are used as a standard to facilitate international trade and financial transactions,
69
+ ensuring consistency and reliability in currency conversion.
70
+ They are typically updated on a daily basis and reflect the market conditions at a specific time.
71
+ Central banks and financial institutions often use these rates to guide their own exchange rates,
72
+ impacting global trade, loans, and investments.
73
+ """
74
+ return await OBBject.from_query(Query(**locals()))
75
+
76
+
77
+ @router.command(
78
+ model="CurrencySnapshots",
79
+ examples=[
80
+ APIEx(parameters={"provider": "fmp"}),
81
+ APIEx(
82
+ description="Get exchange rates from USD and XAU to EUR, JPY, and GBP using 'fmp' as provider.",
83
+ parameters={
84
+ "provider": "fmp",
85
+ "base": "USD,XAU",
86
+ "counter_currencies": "EUR,JPY,GBP",
87
+ "quote_type": "indirect",
88
+ },
89
+ ),
90
+ ],
91
+ )
92
+ async def snapshots(
93
+ cc: CommandContext,
94
+ provider_choices: ProviderChoices,
95
+ standard_params: StandardParams,
96
+ extra_params: ExtraParams,
97
+ ) -> OBBject:
98
+ """Snapshots of currency exchange rates from an indirect or direct perspective of a base currency."""
99
+ return await OBBject.from_query(Query(**locals()))
openbb_platform/extensions/currency/openbb_currency/currency_views.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Views for the Currency Extension."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Dict, Tuple
4
+
5
+ if TYPE_CHECKING:
6
+ from openbb_charting.core.openbb_figure import (
7
+ OpenBBFigure,
8
+ )
9
+
10
+
11
+ class CurrencyViews:
12
+ """Currency Views."""
13
+
14
+ @staticmethod
15
+ def currency_price_historical( # noqa: PLR0912
16
+ **kwargs,
17
+ ) -> Tuple["OpenBBFigure", Dict[str, Any]]:
18
+ """Currency Price Historical Chart."""
19
+ # pylint: disable=import-outside-toplevel
20
+ from openbb_charting.charts.price_historical import price_historical
21
+
22
+ return price_historical(**kwargs)
openbb_platform/extensions/currency/openbb_currency/price/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """The Currency price router init."""
openbb_platform/extensions/currency/openbb_currency/price/price_router.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Price router for Currency."""
2
+
3
+ # pylint: disable=unused-argument
4
+ from openbb_core.app.model.command_context import CommandContext
5
+ from openbb_core.app.model.example import APIEx
6
+ from openbb_core.app.model.obbject import OBBject
7
+ from openbb_core.app.provider_interface import (
8
+ ExtraParams,
9
+ ProviderChoices,
10
+ StandardParams,
11
+ )
12
+ from openbb_core.app.query import Query
13
+ from openbb_core.app.router import Router
14
+
15
+ router = Router(prefix="/price")
16
+
17
+
18
+ # pylint: disable=unused-argument
19
+ @router.command(
20
+ model="CurrencyHistorical",
21
+ examples=[
22
+ APIEx(parameters={"symbol": "EURUSD", "provider": "fmp"}),
23
+ APIEx(
24
+ description="Filter historical data with specific start and end date.",
25
+ parameters={
26
+ "symbol": "EURUSD",
27
+ "start_date": "2023-01-01",
28
+ "end_date": "2023-12-31",
29
+ "provider": "fmp",
30
+ },
31
+ ),
32
+ APIEx(
33
+ description="Get data with different granularity.",
34
+ parameters={"symbol": "EURUSD", "provider": "polygon", "interval": "15m"},
35
+ ),
36
+ ],
37
+ )
38
+ async def historical(
39
+ cc: CommandContext,
40
+ provider_choices: ProviderChoices,
41
+ standard_params: StandardParams,
42
+ extra_params: ExtraParams,
43
+ ) -> OBBject:
44
+ """
45
+ Currency Historical Price. Currency historical data.
46
+
47
+ Currency historical prices refer to the past exchange rates of one currency against
48
+ another over a specific period.
49
+ This data provides insight into the fluctuations and trends in the foreign exchange market,
50
+ helping analysts, traders, and economists understand currency performance,
51
+ evaluate economic health, and make predictions about future movements.
52
+ """
53
+ return await OBBject.from_query(Query(**locals()))
openbb_platform/extensions/currency/openbb_currency/py.typed ADDED
File without changes
openbb_platform/extensions/currency/poetry.lock ADDED
The diff for this file is too large to render. See raw diff
 
openbb_platform/extensions/currency/pyproject.toml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "openbb-currency"
3
+ version = "1.4.1"
4
+ description = "Currency extension for OpenBB"
5
+ authors = ["OpenBB Team <hello@openbb.co>"]
6
+ license = "AGPL-3.0-only"
7
+ readme = "README.md"
8
+ packages = [{ include = "openbb_currency" }]
9
+
10
+ [tool.poetry.dependencies]
11
+ python = ">=3.9.21,<3.13"
12
+ openbb-core = "^1.4.6"
13
+
14
+ [build-system]
15
+ requires = ["poetry-core"]
16
+ build-backend = "poetry.core.masonry.api"
17
+
18
+ [tool.poetry.plugins."openbb_core_extension"]
19
+ currency = "openbb_currency.currency_router:router"
20
+
21
+ [tool.poetry.plugins."openbb_charting_extension"]
22
+ currency = "openbb_currency.currency_views:CurrencyViews"
openbb_platform/extensions/currency/tests/.gitkeep ADDED
File without changes
openbb_platform/extensions/derivatives/README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenBB Derivatives Extension
2
+
3
+ This extension provides derivatives data for the OpenBB Platform.
4
+
5
+ ## Installation
6
+
7
+ To install the extension, run the following command in this folder:
8
+
9
+ ```bash
10
+ pip install openbb-derivatives
11
+ ```
12
+
13
+ Documentation available [here](https://docs.openbb.co/sdk).
openbb_platform/extensions/derivatives/integration/test_derivatives_api.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """API integration tests for the derivatives extension."""
2
+
3
+ import base64
4
+
5
+ import pytest
6
+ import requests
7
+ from extensions.tests.conftest import parametrize
8
+ from openbb_core.env import Env
9
+ from openbb_core.provider.utils.helpers import get_querystring
10
+
11
+ # pylint: disable=too-many-lines,redefined-outer-name
12
+
13
+
14
+ @pytest.fixture(scope="session")
15
+ def headers():
16
+ """Get the headers for the API request."""
17
+ userpass = f"{Env().API_USERNAME}:{Env().API_PASSWORD}"
18
+ userpass_bytes = userpass.encode("ascii")
19
+ base64_bytes = base64.b64encode(userpass_bytes)
20
+
21
+ return {"Authorization": f"Basic {base64_bytes.decode('ascii')}"}
22
+
23
+
24
+ @parametrize(
25
+ "params",
26
+ [
27
+ (
28
+ {
29
+ "provider": "intrinio",
30
+ "symbol": "AAPL",
31
+ "date": "2023-01-25",
32
+ "option_type": None,
33
+ "moneyness": "all",
34
+ "strike_gt": None,
35
+ "strike_lt": None,
36
+ "volume_gt": None,
37
+ "volume_lt": None,
38
+ "oi_gt": None,
39
+ "oi_lt": None,
40
+ "model": "black_scholes",
41
+ "show_extended_price": False,
42
+ "include_related_symbols": False,
43
+ "delay": "delayed",
44
+ }
45
+ ),
46
+ ({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
47
+ ({"provider": "tradier", "symbol": "AAPL"}),
48
+ ({"provider": "yfinance", "symbol": "AAPL"}),
49
+ ({"provider": "deribit", "symbol": "BTC"}),
50
+ (
51
+ {
52
+ "provider": "tmx",
53
+ "symbol": "SHOP",
54
+ "date": "2022-12-28",
55
+ "use_cache": False,
56
+ }
57
+ ),
58
+ ],
59
+ )
60
+ @pytest.mark.integration
61
+ def test_derivatives_options_chains(params, headers):
62
+ """Test the options chains endpoint."""
63
+ params = {p: v for p, v in params.items() if v}
64
+
65
+ query_str = get_querystring(params, [])
66
+ url = f"http://0.0.0.0:8000/api/v1/derivatives/options/chains?{query_str}"
67
+ result = requests.get(url, headers=headers, timeout=10)
68
+ assert isinstance(result, requests.Response)
69
+ assert result.status_code == 200
70
+
71
+
72
+ @parametrize(
73
+ "params",
74
+ [
75
+ (
76
+ {
77
+ "symbol": "AAPL",
78
+ "provider": "intrinio",
79
+ "start_date": "2023-11-20",
80
+ "end_date": None,
81
+ "min_value": None,
82
+ "max_value": None,
83
+ "trade_type": None,
84
+ "sentiment": "neutral",
85
+ "limit": 1000,
86
+ "source": "delayed",
87
+ }
88
+ )
89
+ ],
90
+ )
91
+ @pytest.mark.integration
92
+ def test_derivatives_options_unusual(params, headers):
93
+ """Test the unusual options endpoint."""
94
+ params = {p: v for p, v in params.items() if v}
95
+
96
+ query_str = get_querystring(params, [])
97
+ url = f"http://0.0.0.0:8000/api/v1/derivatives/options/unusual?{query_str}"
98
+ result = requests.get(url, headers=headers, timeout=10)
99
+ assert isinstance(result, requests.Response)
100
+ assert result.status_code == 200
101
+
102
+
103
+ @parametrize(
104
+ "params",
105
+ [
106
+ (
107
+ {
108
+ "provider": "yfinance",
109
+ "interval": "1d",
110
+ "symbol": "CL,BZ",
111
+ "start_date": "2023-01-01",
112
+ "end_date": "2023-06-06",
113
+ "expiration": "2025-12",
114
+ }
115
+ ),
116
+ (
117
+ {
118
+ "provider": "deribit",
119
+ "interval": "1d",
120
+ "symbol": "BTC,ETH",
121
+ "start_date": "2023-01-01",
122
+ "end_date": "2023-06-06",
123
+ }
124
+ ),
125
+ ],
126
+ )
127
+ @pytest.mark.integration
128
+ def test_derivatives_futures_historical(params, headers):
129
+ """Test the futures historical endpoint."""
130
+ params = {p: v for p, v in params.items() if v}
131
+
132
+ query_str = get_querystring(params, [])
133
+ url = f"http://0.0.0.0:8000/api/v1/derivatives/futures/historical?{query_str}"
134
+ result = requests.get(url, headers=headers, timeout=10)
135
+ assert isinstance(result, requests.Response)
136
+ assert result.status_code == 200
137
+
138
+
139
+ @parametrize(
140
+ "params",
141
+ [
142
+ (
143
+ {
144
+ "provider": "yfinance",
145
+ "symbol": "ES",
146
+ "date": None,
147
+ }
148
+ ),
149
+ (
150
+ {
151
+ "provider": "cboe",
152
+ "symbol": "VX_EOD",
153
+ "date": "2024-06-25",
154
+ }
155
+ ),
156
+ ({"provider": "deribit", "date": None, "symbol": "BTC", "hours_ago": 12}),
157
+ ],
158
+ )
159
+ @pytest.mark.integration
160
+ def test_derivatives_futures_curve(params, headers):
161
+ """Test the futures curve endpoint."""
162
+ params = {p: v for p, v in params.items() if v}
163
+
164
+ query_str = get_querystring(params, [])
165
+ url = f"http://0.0.0.0:8000/api/v1/derivatives/futures/curve?{query_str}"
166
+ result = requests.get(url, headers=headers, timeout=10)
167
+ assert isinstance(result, requests.Response)
168
+ assert result.status_code == 200
169
+
170
+
171
+ @parametrize(
172
+ "params",
173
+ [
174
+ ({"provider": "intrinio", "date": None, "only_traded": True}),
175
+ ],
176
+ )
177
+ @pytest.mark.skip(
178
+ reason="This test is skipped because the download is excessively large."
179
+ )
180
+ def test_derivatives_options_snapshots(params, headers):
181
+ """Test the options snapshots endpoint."""
182
+ params = {p: v for p, v in params.items() if v}
183
+
184
+ query_str = get_querystring(params, [])
185
+ url = f"http://0.0.0.0:8000/api/v1/derivatives/options/snapshots?{query_str}"
186
+ result = requests.get(url, headers=headers, timeout=60)
187
+ assert isinstance(result, requests.Response)
188
+ assert result.status_code == 200
189
+
190
+
191
+ @parametrize(
192
+ "params",
193
+ [
194
+ ({"provider": "deribit"}),
195
+ ],
196
+ )
197
+ @pytest.mark.integration
198
+ def test_derivatives_futures_instruments(params, headers):
199
+ """Test the futures instruments endpoint."""
200
+ params = {p: v for p, v in params.items() if v}
201
+
202
+ query_str = get_querystring(params, [])
203
+ url = f"http://0.0.0.0:8000/api/v1/derivatives/futures/instruments?{query_str}"
204
+ result = requests.get(url, headers=headers, timeout=10)
205
+ assert isinstance(result, requests.Response)
206
+ assert result.status_code == 200
207
+
208
+
209
+ @parametrize(
210
+ "params",
211
+ [
212
+ ({"provider": "deribit", "symbol": "ETH-PERPETUAL"}),
213
+ ],
214
+ )
215
+ @pytest.mark.integration
216
+ def test_derivatives_futures_info(params, headers):
217
+ """Test the futures info endpoint."""
218
+ params = {p: v for p, v in params.items() if v}
219
+
220
+ query_str = get_querystring(params, [])
221
+ url = f"http://0.0.0.0:8000/api/v1/derivatives/futures/info?{query_str}"
222
+ result = requests.get(url, headers=headers, timeout=10)
223
+ assert isinstance(result, requests.Response)
224
+ assert result.status_code == 200
openbb_platform/extensions/derivatives/integration/test_derivatives_python.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Python interface integration tests for the derivatives extension."""
2
+
3
+ import pytest
4
+ from extensions.tests.conftest import parametrize
5
+ from openbb_core.app.model.obbject import OBBject
6
+
7
+ # pylint: disable=too-many-lines,redefined-outer-name
8
+ # pylint: disable=import-outside-toplevel,inconsistent-return-statements
9
+
10
+
11
+ @pytest.fixture(scope="session")
12
+ def obb(pytestconfig):
13
+ """Fixture to setup obb."""
14
+ if pytestconfig.getoption("markexpr") != "not integration":
15
+ import openbb
16
+
17
+ return openbb.obb
18
+
19
+
20
+ @parametrize(
21
+ "params",
22
+ [
23
+ (
24
+ {
25
+ "provider": "intrinio",
26
+ "symbol": "AAPL",
27
+ "date": "2023-01-25",
28
+ "option_type": None,
29
+ "moneyness": "all",
30
+ "strike_gt": None,
31
+ "strike_lt": None,
32
+ "volume_gt": None,
33
+ "volume_lt": None,
34
+ "oi_gt": None,
35
+ "oi_lt": None,
36
+ "model": "black_scholes",
37
+ "show_extended_price": False,
38
+ "include_related_symbols": False,
39
+ "delay": "delayed",
40
+ }
41
+ ),
42
+ ({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
43
+ ({"provider": "tradier", "symbol": "AAPL"}),
44
+ ({"provider": "yfinance", "symbol": "AAPL"}),
45
+ ({"provider": "deribit", "symbol": "BTC"}),
46
+ (
47
+ {
48
+ "provider": "tmx",
49
+ "symbol": "SHOP",
50
+ "date": "2022-12-28",
51
+ "use_cache": False,
52
+ }
53
+ ),
54
+ ],
55
+ )
56
+ @pytest.mark.integration
57
+ def test_derivatives_options_chains(params, obb):
58
+ """Test the options chains endpoint."""
59
+ result = obb.derivatives.options.chains(**params)
60
+ assert result
61
+ assert isinstance(result, OBBject)
62
+ result = result.results # type: ignore
63
+ list_msg = "Unexpected data format, expected List"
64
+ oi_msg = "Unexpected keys in total_oi property, expected ['total', 'expiration', 'strike']"
65
+ assert isinstance(result.expirations, list), list_msg # type: ignore
66
+ assert isinstance(result.strikes, list), list_msg # type: ignore
67
+ assert isinstance(result.contract_symbol, list), list_msg # type: ignore
68
+ assert hasattr(result, "total_oi"), "Missing total_oi property" # type: ignore
69
+ assert isinstance(result.total_oi, dict), "Unexpected property format, expected dictionary." # type: ignore
70
+ assert list(result.total_oi) == ["total", "expiration", "strike"], oi_msg # type: ignore
71
+ assert hasattr(result, "dataframe"), "Missing dataframe attribute" # type: ignore
72
+ assert result.has_iv, "Expected implied volatility data" # type: ignore
73
+ assert len(getattr(result, "dataframe", [])) == len(result.contract_symbol) # type: ignore
74
+
75
+
76
+ @parametrize(
77
+ "params",
78
+ [
79
+ (
80
+ {
81
+ "symbol": "AAPL",
82
+ "provider": "intrinio",
83
+ "start_date": "2023-11-20",
84
+ "end_date": None,
85
+ "min_value": None,
86
+ "max_value": None,
87
+ "trade_type": None,
88
+ "sentiment": "neutral",
89
+ "limit": 1000,
90
+ "source": "delayed",
91
+ }
92
+ )
93
+ ],
94
+ )
95
+ @pytest.mark.integration
96
+ def test_derivatives_options_unusual(params, obb):
97
+ """Test the unusual options endpoint."""
98
+ result = obb.derivatives.options.unusual(**params)
99
+ assert result
100
+ assert isinstance(result, OBBject)
101
+ assert len(result.results) > 0
102
+
103
+
104
+ @parametrize(
105
+ "params",
106
+ [
107
+ (
108
+ {
109
+ "provider": "yfinance",
110
+ "interval": "1d",
111
+ "symbol": "CL,BZ",
112
+ "start_date": "2023-01-01",
113
+ "end_date": "2023-06-06",
114
+ "expiration": "2025-12",
115
+ }
116
+ ),
117
+ (
118
+ {
119
+ "provider": "deribit",
120
+ "interval": "1d",
121
+ "symbol": "BTC,ETH",
122
+ "start_date": "2023-01-01",
123
+ "end_date": "2023-06-06",
124
+ }
125
+ ),
126
+ ],
127
+ )
128
+ @pytest.mark.integration
129
+ def test_derivatives_futures_historical(params, obb):
130
+ """Test the futures historical endpoint."""
131
+ result = obb.derivatives.futures.historical(**params)
132
+ assert result
133
+ assert isinstance(result, OBBject)
134
+ assert len(result.results) > 0
135
+
136
+
137
+ @parametrize(
138
+ "params",
139
+ [
140
+ ({"provider": "yfinance", "symbol": "ES", "date": None}),
141
+ ({"provider": "cboe", "symbol": "VX", "date": "2024-06-25"}),
142
+ ({"provider": "deribit", "date": None, "symbol": "BTC", "hours_ago": 12}),
143
+ ],
144
+ )
145
+ @pytest.mark.integration
146
+ def test_derivatives_futures_curve(params, obb):
147
+ """Test the futures curve endpoint."""
148
+ result = obb.derivatives.futures.curve(**params)
149
+ assert result
150
+ assert isinstance(result, OBBject)
151
+ assert len(result.results) > 0
152
+
153
+
154
+ @parametrize(
155
+ "params",
156
+ [
157
+ ({"provider": "intrinio", "date": None, "only_traded": True}),
158
+ ],
159
+ )
160
+ @pytest.mark.skip(
161
+ reason="This test is skipped because the download is excessively large."
162
+ )
163
+ def test_derivatives_options_snapshots(params, obb):
164
+ """Test the options snapshots endpoint."""
165
+ result = obb.derivatives.options.snapshots(**params)
166
+ assert result
167
+ assert isinstance(result, OBBject)
168
+ assert len(result.results) > 0
169
+
170
+
171
+ @parametrize(
172
+ "params",
173
+ [
174
+ ({"provider": "deribit"}),
175
+ ],
176
+ )
177
+ @pytest.mark.integration
178
+ def test_derivatives_futures_instruments(params, obb):
179
+ """Test the futures instruments endpoint."""
180
+ result = obb.derivatives.futures.instruments(**params)
181
+ assert result
182
+ assert isinstance(result, OBBject)
183
+ assert len(result.results) > 0
184
+
185
+
186
+ @parametrize(
187
+ "params",
188
+ [
189
+ ({"provider": "deribit", "symbol": "ETH-PERPETUAL"}),
190
+ ],
191
+ )
192
+ @pytest.mark.integration
193
+ def test_derivatives_futures_info(params, obb):
194
+ """Test the futures info endpoint."""
195
+ result = obb.derivatives.futures.info(**params)
196
+ assert result
197
+ assert isinstance(result, OBBject)
198
+ assert len(result.results) > 0
openbb_platform/extensions/derivatives/openbb_derivatives/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Options."""
openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_router.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ """Derivatives Router."""
2
+
3
+ from openbb_core.app.router import Router
4
+
5
+ from openbb_derivatives.futures.futures_router import router as futures_router
6
+ from openbb_derivatives.options.options_router import router as options_router
7
+
8
+ router = Router(prefix="", description="Derivatives market data.")
9
+ router.include_router(options_router)
10
+ router.include_router(futures_router)
openbb_platform/extensions/derivatives/openbb_derivatives/derivatives_views.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Views for the Derivatives Extension."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Dict, Tuple
4
+
5
+ if TYPE_CHECKING:
6
+ from openbb_charting.core.openbb_figure import OpenBBFigure
7
+
8
+
9
+ class DerivativesViews:
10
+ """Derivatives Views."""
11
+
12
+ @staticmethod
13
+ def derivatives_futures_historical( # noqa: PLR0912
14
+ **kwargs,
15
+ ) -> Tuple["OpenBBFigure", Dict[str, Any]]:
16
+ """Get Derivatives Price Historical Chart."""
17
+ # pylint: disable=import-outside-toplevel
18
+ from openbb_charting.charts.price_historical import price_historical
19
+
20
+ kwargs.update({"candles": False, "same_axis": False})
21
+
22
+ return price_historical(**kwargs)
23
+
24
+ @staticmethod
25
+ def derivatives_futures_curve( # noqa: PLR0912
26
+ **kwargs,
27
+ ) -> Tuple["OpenBBFigure", Dict[str, Any]]:
28
+ """Futures curve chart. All parameters are optional, and are kwargs.
29
+ Parameters can be directly accessed from the function end point by
30
+ entering as a nested dictionary to the 'chart_params' key.
31
+
32
+ From the API, `chart_params` must be passed as a JSON in the request body with `extra_params`.
33
+
34
+ If using the chart post-request, the parameters are passed directly
35
+ as `key=value` pairs in the `charting.to_chart` or `charting.show` methods.
36
+
37
+ Parameters
38
+ ----------
39
+ data : Optional[Union[List[Data], DataFrame]]
40
+ Data for the chart. Required fields are: 'expiration' and 'price'.
41
+ Multiple dates will be plotted on the same chart.
42
+ If not supplied, the original OBBject.results will be used.
43
+ If a DataFrame is supplied, flat data is expected, without a set index.
44
+ title: Optional[str]
45
+ Title for the chart. If not supplied, a default title will be used.
46
+ colors: Optional[List[str]]
47
+ List of colors to use for the chart. If not supplied, the default colorway will be used.
48
+ Colors should be in hex format, or named Plotly colors. Invalid colors will raise a Plotly error.
49
+ layout_kwargs: Optional[Dict[str, Any]]
50
+ Additional layout parameters for the chart, passed directly to `figure.update_layout` before output.
51
+ See Plotly documentation for available options.
52
+
53
+ Returns
54
+ -------
55
+ Tuple[OpenBBFigure, Dict[str, Any]]
56
+ Tuple with the OpenBBFigure object, and the JSON-serialized content.
57
+ If using the API, only the JSON content will be returned.
58
+
59
+ Examples
60
+ --------
61
+ ```python
62
+ from openbb import obb
63
+ data = obb.derivatives.futures.curve(symbol="vx", provider="cboe", date=["2020-03-31", "2024-06-28"], chart=True)
64
+ data.show()
65
+ ```
66
+
67
+ Redraw the chart, from the same data, with a custom colorway and title:
68
+
69
+ ```python
70
+ data.charting.to_chart(colors=["green", "red"], title="VIX Futures Curve - 2020 vs. 2024")
71
+ ```
72
+ """
73
+ # pylint: disable=import-outside-toplevel
74
+ from openbb_charting.core.chart_style import ChartStyle
75
+ from openbb_charting.core.openbb_figure import OpenBBFigure
76
+ from openbb_charting.styles.colors import LARGE_CYCLER
77
+ from openbb_core.app.model.abstract.error import OpenBBError
78
+ from openbb_core.provider.abstract.data import Data
79
+ from pandas import DataFrame, to_datetime
80
+
81
+ data = kwargs.get("data")
82
+ symbol = kwargs.get("standard_params", {}).get("symbol", "")
83
+ df: DataFrame = DataFrame()
84
+ if data:
85
+ if isinstance(data, DataFrame) and not data.empty: # noqa: SIM108
86
+ df = data
87
+ elif isinstance(data, (list, Data)):
88
+ df = DataFrame([d.model_dump(exclude_none=True, exclude_unset=True) for d in data]) # type: ignore
89
+ else:
90
+ pass
91
+ else:
92
+ df = DataFrame(
93
+ [
94
+ d.model_dump(exclude_none=True, exclude_unset=True) # type: ignore
95
+ for d in kwargs["obbject_item"]
96
+ ]
97
+ if isinstance(kwargs.get("obbject_item"), list)
98
+ else kwargs["obbject_item"].model_dump(exclude_none=True, exclude_unset=True) # type: ignore
99
+ )
100
+
101
+ if df.empty:
102
+ raise OpenBBError("Error: No data to plot.")
103
+
104
+ if "expiration" not in df.columns:
105
+ raise OpenBBError("Expiration field not found in the data.")
106
+
107
+ if "price" not in df.columns:
108
+ raise ValueError("Price field not found in the data.")
109
+
110
+ provider = kwargs.get("provider", "")
111
+
112
+ if provider != "deribit":
113
+ df["expiration"] = df["expiration"].apply(to_datetime).dt.strftime("%b-%Y")
114
+
115
+ if (
116
+ provider == "cboe"
117
+ and "date" in df.columns
118
+ and len(df["date"].unique()) > 1
119
+ and "symbol" in df.columns
120
+ ):
121
+ df["expiration"] = df.symbol
122
+
123
+ # Use a complete list of expirations to categorize the x-axis across all dates.
124
+ expirations = df["expiration"].unique().tolist()
125
+
126
+ # Use the supplied colors, if any.
127
+ colors = kwargs.get("colors", [])
128
+ if not colors:
129
+ colors = LARGE_CYCLER
130
+ color_count = 0
131
+
132
+ figure = OpenBBFigure().create_subplots(shared_xaxes=True)
133
+ figure.update_layout(ChartStyle().plotly_template.get("layout", {}))
134
+ text_color = "white" if ChartStyle().plt_style == "dark" else "black"
135
+
136
+ def create_fig(figure, df, dates, color_count):
137
+ """Create a scatter for each date in the data."""
138
+ for date in dates:
139
+ color = colors[color_count % len(colors)]
140
+ plot_df = (
141
+ df[df["date"].astype(str) == date].copy()
142
+ if "date" in df.columns
143
+ else df.copy()
144
+ )
145
+ plot_df = plot_df.drop(
146
+ columns=["date"] if "date" in plot_df.columns else []
147
+ ).rename(columns={"expiration": "Expiration", "price": "Price"})
148
+ figure.add_scatter(
149
+ x=plot_df["Expiration"],
150
+ y=plot_df["Price"],
151
+ mode="lines+markers",
152
+ name=date,
153
+ line=dict(width=3, color=color),
154
+ marker=dict(size=10, color=color),
155
+ hovertemplate=(
156
+ "Expiration: %{x}<br>Price: $%{y}<extra></extra>"
157
+ if len(dates) == 1
158
+ else "%{fullData.name}<br>Expiration: %{x}<br>Price: $%{y}<extra></extra>"
159
+ ),
160
+ )
161
+ color_count += 1
162
+ return figure, color_count
163
+
164
+ dates = (
165
+ df.date.astype(str).unique().tolist()
166
+ if "date" in df.columns
167
+ else ["Current"]
168
+ )
169
+
170
+ if provider == "deribit" and "hours_ago" in df.columns:
171
+ dates = [
172
+ str(d) + " Hours Ago" if d > 0 else "Current"
173
+ for d in df["hours_ago"].unique().tolist()
174
+ ]
175
+ df.loc[:, "date"] = df["hours_ago"].apply(
176
+ lambda x: str(x) + " Hours Ago" if x > 0 else "Current"
177
+ )
178
+ figure, color_count = create_fig(figure, df, dates, color_count)
179
+
180
+ # Set the title for the chart
181
+ title: str = ""
182
+ if provider == "cboe":
183
+ vx_eod_symbols = ["vx", "vix", "vx_eod", "^vix"]
184
+ title = (
185
+ "VIX EOD Futures Curve"
186
+ if symbol.lower() in vx_eod_symbols
187
+ else "VIX Mid-Morning TWAP Futures Curve"
188
+ )
189
+ if len(dates) == 1 and dates[0] != "Current":
190
+ title = f"{title} for {dates[0]}"
191
+ else:
192
+ title = f"{symbol.upper()} Futures Curve"
193
+
194
+ # Use the supplied title, if any.
195
+ title = kwargs.get("title", title)
196
+
197
+ # Update the layout of the figure.
198
+ figure.update_layout(
199
+ title=dict(text=title, x=0.5, font=dict(size=20)),
200
+ plot_bgcolor=(
201
+ "rgba(0,0,0,0)" if text_color == "white" else "rgba(255,255,255,0)"
202
+ ),
203
+ paper_bgcolor=(
204
+ "rgba(0,0,0,0)" if text_color == "white" else "rgba(255,255,255,0)"
205
+ ),
206
+ xaxis=dict(
207
+ title="",
208
+ ticklen=0,
209
+ showgrid=False,
210
+ type="category",
211
+ categoryorder="array",
212
+ categoryarray=expirations,
213
+ ),
214
+ yaxis=dict(
215
+ title="Price ($)",
216
+ ticklen=0,
217
+ showgrid=True,
218
+ gridcolor="rgba(128,128,128,0.3)",
219
+ ),
220
+ legend=dict(
221
+ orientation="v",
222
+ yanchor="top",
223
+ xanchor="right",
224
+ y=0.95,
225
+ x=0,
226
+ xref="paper",
227
+ font=dict(size=12),
228
+ bgcolor=(
229
+ "rgba(0,0,0,0)" if text_color == "white" else "rgba(255,255,255,0)"
230
+ ),
231
+ ),
232
+ margin=dict(
233
+ b=10,
234
+ t=10,
235
+ ),
236
+ )
237
+
238
+ layout_kwargs = kwargs.get("layout_kwargs", {})
239
+ if layout_kwargs:
240
+ figure.update_layout(layout_kwargs)
241
+
242
+ content = figure.show(external=True).to_plotly_json()
243
+
244
+ return figure, content
openbb_platform/extensions/derivatives/openbb_derivatives/futures/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Futures."""
openbb_platform/extensions/derivatives/openbb_derivatives/futures/futures_router.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Futures Router."""
2
+
3
+ from openbb_core.app.model.command_context import CommandContext
4
+ from openbb_core.app.model.example import APIEx
5
+ from openbb_core.app.model.obbject import OBBject
6
+ from openbb_core.app.provider_interface import (
7
+ ExtraParams,
8
+ ProviderChoices,
9
+ StandardParams,
10
+ )
11
+ from openbb_core.app.query import Query
12
+ from openbb_core.app.router import Router
13
+
14
+ router = Router(prefix="/futures")
15
+
16
+
17
+ # pylint: disable=unused-argument
18
+ @router.command(
19
+ model="FuturesHistorical",
20
+ examples=[
21
+ APIEx(parameters={"symbol": "ES", "provider": "yfinance"}),
22
+ APIEx(
23
+ description="Enter multiple symbols.",
24
+ parameters={"symbol": "ES,NQ", "provider": "yfinance"},
25
+ ),
26
+ APIEx(
27
+ description='Enter expiration dates as "YYYY-MM".',
28
+ parameters={
29
+ "symbol": "ES",
30
+ "provider": "yfinance",
31
+ "expiration": "2025-12",
32
+ },
33
+ ),
34
+ ],
35
+ )
36
+ async def historical(
37
+ cc: CommandContext,
38
+ provider_choices: ProviderChoices,
39
+ standard_params: StandardParams,
40
+ extra_params: ExtraParams,
41
+ ) -> OBBject:
42
+ """Historical futures prices."""
43
+ return await OBBject.from_query(Query(**locals()))
44
+
45
+
46
+ @router.command(
47
+ model="FuturesCurve",
48
+ examples=[
49
+ APIEx(parameters={"symbol": "VX", "provider": "cboe", "date": "2024-06-25"}),
50
+ APIEx(
51
+ parameters={"symbol": "NG", "provider": "yfinance"},
52
+ ),
53
+ ],
54
+ )
55
+ async def curve(
56
+ cc: CommandContext,
57
+ provider_choices: ProviderChoices,
58
+ standard_params: StandardParams,
59
+ extra_params: ExtraParams,
60
+ ) -> OBBject:
61
+ """Futures Term Structure, current or historical."""
62
+ return await OBBject.from_query(Query(**locals()))
63
+
64
+
65
+ @router.command(
66
+ model="FuturesInstruments",
67
+ examples=[
68
+ APIEx(parameters={"provider": "deribit"}),
69
+ ],
70
+ )
71
+ async def instruments(
72
+ cc: CommandContext,
73
+ provider_choices: ProviderChoices,
74
+ standard_params: StandardParams,
75
+ extra_params: ExtraParams,
76
+ ) -> OBBject:
77
+ """Get reference data for available futures instruments by provider."""
78
+ return await OBBject.from_query(Query(**locals()))
79
+
80
+
81
+ @router.command(
82
+ model="FuturesInfo",
83
+ examples=[
84
+ APIEx(parameters={"provider": "deribit", "symbol": "BTC"}),
85
+ APIEx(parameters={"provider": "deribit", "symbol": "SOLUSDC"}),
86
+ APIEx(parameters={"provider": "deribit", "symbol": "SOL_USDC-PERPETUAL"}),
87
+ APIEx(parameters={"provider": "deribit", "symbol": "BTC,ETH"}),
88
+ ],
89
+ )
90
+ async def info(
91
+ cc: CommandContext,
92
+ provider_choices: ProviderChoices,
93
+ standard_params: StandardParams,
94
+ extra_params: ExtraParams,
95
+ ) -> OBBject:
96
+ """Get current trading statistics by futures contract symbol."""
97
+ return await OBBject.from_query(Query(**locals()))
openbb_platform/extensions/derivatives/openbb_derivatives/options/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Options."""
openbb_platform/extensions/derivatives/openbb_derivatives/options/options_router.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Options Router."""
2
+
3
+ from openbb_core.app.model.command_context import CommandContext
4
+ from openbb_core.app.model.example import APIEx
5
+ from openbb_core.app.model.obbject import OBBject
6
+ from openbb_core.app.provider_interface import (
7
+ ExtraParams,
8
+ ProviderChoices,
9
+ StandardParams,
10
+ )
11
+ from openbb_core.app.query import Query
12
+ from openbb_core.app.router import Router
13
+
14
+ router = Router(prefix="/options")
15
+
16
+ # pylint: disable=unused-argument
17
+
18
+
19
+ @router.command(
20
+ model="OptionsChains",
21
+ examples=[
22
+ APIEx(parameters={"symbol": "AAPL", "provider": "intrinio"}),
23
+ APIEx(
24
+ description='Use the "date" parameter to get the end-of-day-data for a specific date, where supported.',
25
+ parameters={"symbol": "AAPL", "date": "2023-01-25", "provider": "intrinio"},
26
+ ),
27
+ ],
28
+ )
29
+ async def chains(
30
+ cc: CommandContext,
31
+ provider_choices: ProviderChoices,
32
+ standard_params: StandardParams,
33
+ extra_params: ExtraParams,
34
+ ) -> OBBject:
35
+ """Get the complete options chain for a ticker."""
36
+ return await OBBject.from_query(Query(**locals()))
37
+
38
+
39
+ @router.command(
40
+ model="OptionsUnusual",
41
+ examples=[
42
+ APIEx(parameters={"symbol": "TSLA", "provider": "intrinio"}),
43
+ APIEx(
44
+ description="Use the 'symbol' parameter to get the most recent activity for a specific symbol.",
45
+ parameters={"symbol": "TSLA", "provider": "intrinio"},
46
+ ),
47
+ ],
48
+ )
49
+ async def unusual(
50
+ cc: CommandContext,
51
+ provider_choices: ProviderChoices,
52
+ standard_params: StandardParams,
53
+ extra_params: ExtraParams,
54
+ ) -> OBBject:
55
+ """Get the complete options chain for a ticker."""
56
+ return await OBBject.from_query(Query(**locals()))
57
+
58
+
59
+ @router.command(
60
+ model="OptionsSnapshots",
61
+ examples=[
62
+ APIEx(
63
+ parameters={"provider": "intrinio"},
64
+ ),
65
+ ],
66
+ )
67
+ async def snapshots(
68
+ cc: CommandContext,
69
+ provider_choices: ProviderChoices,
70
+ standard_params: StandardParams,
71
+ extra_params: ExtraParams,
72
+ ) -> OBBject:
73
+ """Get a snapshot of the options market universe."""
74
+ return await OBBject.from_query(Query(**locals()))
openbb_platform/extensions/derivatives/poetry.lock ADDED
The diff for this file is too large to render. See raw diff
 
openbb_platform/extensions/derivatives/pyproject.toml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "openbb-derivatives"
3
+ version = "1.4.1"
4
+ description = "Derivatives extension for OpenBB"
5
+ authors = ["OpenBB Team <hello@openbb.co>"]
6
+ license = "AGPL-3.0-only"
7
+ readme = "README.md"
8
+ packages = [{ include = "openbb_derivatives" }]
9
+
10
+ [tool.poetry.dependencies]
11
+ python = ">=3.9.21,<3.13"
12
+ openbb-core = "^1.4.6"
13
+
14
+ [build-system]
15
+ requires = ["poetry-core"]
16
+ build-backend = "poetry.core.masonry.api"
17
+
18
+ [tool.poetry.plugins."openbb_core_extension"]
19
+ derivatives = "openbb_derivatives.derivatives_router:router"
20
+
21
+ [tool.poetry.plugins."openbb_charting_extension"]
22
+ derivatives = "openbb_derivatives.derivatives_views:DerivativesViews"
openbb_platform/extensions/derivatives/tests/.gitkeep ADDED
File without changes
openbb_platform/extensions/devtools/README.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # The OpenBB DevTools Extension
2
+
3
+ This extension aggregates the dependencies that facilitate a nice development experience
4
+ for OpenBB. It does not contain any code itself, but rather pulls in the following dependencies:
5
+
6
+ - Linters (ruff, pylint, mypy)
7
+ - Code formatters (black)
8
+ - Code quality tools (bandit)
9
+ - Pre-commit hooks (pre-commit)
10
+ - CI/CD configuration (tox, pytest, pytest-cov)
11
+ - Jupyter kernel (ipykernel)
12
+ - ... add your productivity booster here ...
13
+
14
+ ## Installation
15
+
16
+ The extension is included into the dev_install.py script.
17
+
18
+ Standalone installation:
19
+
20
+ ```bash
21
+ pip install openbb-devtools
22
+ ```
openbb_platform/extensions/devtools/integration/.gitkeep ADDED
File without changes