Upload 36 files
Browse files- openbb_platform/providers/tradier/README.md +24 -0
- openbb_platform/providers/tradier/__init__.py +1 -0
- openbb_platform/providers/tradier/openbb_tradier/__init__.py +31 -0
- openbb_platform/providers/tradier/openbb_tradier/models/__init__.py +1 -0
- openbb_platform/providers/tradier/openbb_tradier/models/equity_historical.py +195 -0
- openbb_platform/providers/tradier/openbb_tradier/models/equity_quote.py +294 -0
- openbb_platform/providers/tradier/openbb_tradier/models/equity_search.py +130 -0
- openbb_platform/providers/tradier/openbb_tradier/models/options_chains.py +263 -0
- openbb_platform/providers/tradier/openbb_tradier/py.typed +0 -0
- openbb_platform/providers/tradier/openbb_tradier/utils/__init__.py +1 -0
- openbb_platform/providers/tradier/openbb_tradier/utils/constants.py +53 -0
- openbb_platform/providers/tradier/poetry.lock +0 -0
- openbb_platform/providers/tradier/pyproject.toml +19 -0
- openbb_platform/providers/tradier/tests/__init__.py +1 -0
- openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_derivatives_options_chains_fetcher_urllib3_v1.yaml +0 -0
- openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_derivatives_options_chains_fetcher_urllib3_v2.yaml +0 -0
- openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_historical_fetcher_urllib3_v1.yaml +40 -0
- openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_historical_fetcher_urllib3_v2.yaml +40 -0
- openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_quote_fetcher_urllib3_v1.yaml +42 -0
- openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_quote_fetcher_urllib3_v2.yaml +42 -0
- openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_search_fetcher_urllib3_v1.yaml +124 -0
- openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_search_fetcher_urllib3_v2.yaml +132 -0
- openbb_platform/providers/tradier/tests/test_tradier_fetchers.py +75 -0
- openbb_platform/providers/tradingeconomics/README.md +13 -0
- openbb_platform/providers/tradingeconomics/__init__.py +1 -0
- openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/__init__.py +19 -0
- openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/models/economic_calendar.py +270 -0
- openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/countries.py +235 -0
- openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/helpers.py +26 -0
- openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/url_generator.py +97 -0
- openbb_platform/providers/tradingeconomics/poetry.lock +0 -0
- openbb_platform/providers/tradingeconomics/pyproject.toml +19 -0
- openbb_platform/providers/tradingeconomics/tests/record/http/test_tradingeconomics_fetchers/test_tradingeconomics_economic_calendar_fetcher_urllib3_v1.yaml +0 -0
- openbb_platform/providers/tradingeconomics/tests/record/http/test_tradingeconomics_fetchers/test_tradingeconomics_economic_calendar_fetcher_urllib3_v2.yaml +0 -0
- openbb_platform/providers/tradingeconomics/tests/test_tradingeconomics_fetchers.py +35 -0
- openbb_platform/providers/tradingeconomics/tests/test_url_generator.py +82 -0
openbb_platform/providers/tradier/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenBB Tradier Provider
|
| 2 |
+
|
| 3 |
+
This extension integrates the [Tradier](https://tradier.com) data provider into the OpenBB Platform.
|
| 4 |
+
|
| 5 |
+
## Installation
|
| 6 |
+
|
| 7 |
+
To install the extension:
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
pip install openbb-tradier
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Documentation available [here](https://docs.openbb.co/platform/developer_guide/contributing).
|
| 14 |
+
|
| 15 |
+
## Authorization
|
| 16 |
+
|
| 17 |
+
This extension requires two authorization fields:
|
| 18 |
+
|
| 19 |
+
- 'tradier_api_key'
|
| 20 |
+
- 'tradier_account_type'
|
| 21 |
+
|
| 22 |
+
Where the account type is either "sandbox" or "live".
|
| 23 |
+
|
| 24 |
+
Add these to the file, under 'credentials': `~/.openbb_platform/user_settings.json`
|
openbb_platform/providers/tradier/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Tradier provider."""
|
openbb_platform/providers/tradier/openbb_tradier/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tradier Provider Module."""
|
| 2 |
+
|
| 3 |
+
from openbb_core.provider.abstract.provider import Provider
|
| 4 |
+
from openbb_tradier.models.equity_historical import TradierEquityHistoricalFetcher
|
| 5 |
+
from openbb_tradier.models.equity_quote import TradierEquityQuoteFetcher
|
| 6 |
+
from openbb_tradier.models.equity_search import TradierEquitySearchFetcher
|
| 7 |
+
from openbb_tradier.models.options_chains import TradierOptionsChainsFetcher
|
| 8 |
+
|
| 9 |
+
tradier_provider = Provider(
|
| 10 |
+
name="tradier",
|
| 11 |
+
website="https://tradier.com",
|
| 12 |
+
description="""Tradier provides a full range of services in a scalable, secure,
|
| 13 |
+
and easy-to-use REST-based API for businesses and individual developers.
|
| 14 |
+
Fast, secure, simple. Start in minutes.
|
| 15 |
+
Get access to trading, account management, and market-data for
|
| 16 |
+
Tradier Brokerage accounts through our APIs.""",
|
| 17 |
+
credentials=[
|
| 18 |
+
"api_key",
|
| 19 |
+
"account_type",
|
| 20 |
+
], # account_type is either "sandbox" or "live"
|
| 21 |
+
fetcher_dict={
|
| 22 |
+
"EquityHistorical": TradierEquityHistoricalFetcher,
|
| 23 |
+
"EtfHistorical": TradierEquityHistoricalFetcher,
|
| 24 |
+
"EquityQuote": TradierEquityQuoteFetcher,
|
| 25 |
+
"EquitySearch": TradierEquitySearchFetcher,
|
| 26 |
+
"OptionsChains": TradierOptionsChainsFetcher,
|
| 27 |
+
},
|
| 28 |
+
repr_name="Tradier",
|
| 29 |
+
deprecated_credentials={"API_TRADIER_TOKEN": "tradier_api_key"},
|
| 30 |
+
instructions='Go to: https://documentation.tradier.com\n\n\n\nClick on, "Open Account", to start the sign-up process. After the account has been setup, navigate to [Tradier Broker Dash](https://dash.tradier.com/login?redirect=settings.api) and create the application. Request a sandbox access token.', # noqa: E501 pylint: disable=line-too-long
|
| 31 |
+
)
|
openbb_platform/providers/tradier/openbb_tradier/models/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Tradier provider models."""
|
openbb_platform/providers/tradier/openbb_tradier/models/equity_historical.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tradier Equity Historical Model."""
|
| 2 |
+
|
| 3 |
+
# pylint: disable = unused-argument
|
| 4 |
+
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
from typing import Any, Dict, List, Literal, Optional
|
| 7 |
+
from warnings import warn
|
| 8 |
+
|
| 9 |
+
from openbb_core.app.model.abstract.error import OpenBBError
|
| 10 |
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
| 11 |
+
from openbb_core.provider.standard_models.equity_historical import (
|
| 12 |
+
EquityHistoricalData,
|
| 13 |
+
EquityHistoricalQueryParams,
|
| 14 |
+
)
|
| 15 |
+
from openbb_core.provider.utils.descriptions import (
|
| 16 |
+
QUERY_DESCRIPTIONS,
|
| 17 |
+
)
|
| 18 |
+
from openbb_core.provider.utils.errors import EmptyDataError
|
| 19 |
+
from openbb_tradier.utils.constants import INTERVALS_DICT
|
| 20 |
+
from pydantic import Field
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TradierEquityHistoricalQueryParams(EquityHistoricalQueryParams):
|
| 24 |
+
"""Tradier Equity Historical Query."""
|
| 25 |
+
|
| 26 |
+
__json_schema_extra__ = {
|
| 27 |
+
"symbol": {"multiple_items_allowed": True},
|
| 28 |
+
"interval": {"choices": ["1m", "5m", "15m", "1d", "1W", "1M"]},
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
interval: Literal["1m", "5m", "15m", "1d", "1W", "1M"] = Field(
|
| 32 |
+
description=QUERY_DESCRIPTIONS.get("interval", ""),
|
| 33 |
+
default="1d",
|
| 34 |
+
)
|
| 35 |
+
extended_hours: bool = Field(
|
| 36 |
+
default=False,
|
| 37 |
+
description="Include Pre and Post market data.",
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class TradierEquityHistoricalData(EquityHistoricalData):
|
| 42 |
+
"""Tradier Equity Historical Data."""
|
| 43 |
+
|
| 44 |
+
__alias_dict__ = {"date": "timestamp", "last_price": "price"}
|
| 45 |
+
|
| 46 |
+
last_price: Optional[float] = Field(
|
| 47 |
+
default=None, description="The last price of the equity."
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class TradierEquityHistoricalFetcher(
|
| 52 |
+
Fetcher[TradierEquityHistoricalQueryParams, List[TradierEquityHistoricalData]]
|
| 53 |
+
):
|
| 54 |
+
"""Tradier Equity Historical Fetcher."""
|
| 55 |
+
|
| 56 |
+
@staticmethod
|
| 57 |
+
def transform_query(params: Dict[str, Any]) -> TradierEquityHistoricalQueryParams:
|
| 58 |
+
"""Transform the query."""
|
| 59 |
+
# pylint: disable=import-outside-toplevel
|
| 60 |
+
from datetime import timedelta
|
| 61 |
+
|
| 62 |
+
if params.get("interval") in ["1d", "1W", "1M"]:
|
| 63 |
+
if params.get("start_date") is None:
|
| 64 |
+
params["start_date"] = (datetime.now() - timedelta(days=365)).date()
|
| 65 |
+
if params.get("end_date") is None:
|
| 66 |
+
params["end_date"] = datetime.now().date()
|
| 67 |
+
|
| 68 |
+
if params.get("interval") in ["1m", "5m", "15m"]:
|
| 69 |
+
interval_dict = {
|
| 70 |
+
"1m": 20,
|
| 71 |
+
"5m": 55,
|
| 72 |
+
"15m": 55,
|
| 73 |
+
}
|
| 74 |
+
params["start_date"] = (
|
| 75 |
+
datetime.now() - timedelta(days=interval_dict[params["interval"]])
|
| 76 |
+
).strftime( # type: ignore
|
| 77 |
+
"%Y-%m-%d"
|
| 78 |
+
)
|
| 79 |
+
params["end_date"] = datetime.now().strftime("%Y-%m-%d")
|
| 80 |
+
|
| 81 |
+
return TradierEquityHistoricalQueryParams(**params)
|
| 82 |
+
|
| 83 |
+
@staticmethod
|
| 84 |
+
async def aextract_data(
|
| 85 |
+
query: TradierEquityHistoricalQueryParams,
|
| 86 |
+
credentials: Optional[Dict[str, str]],
|
| 87 |
+
**kwargs: Any,
|
| 88 |
+
) -> List[Dict]:
|
| 89 |
+
"""Return the raw data from the Tradier endpoint."""
|
| 90 |
+
# pylint: disable=import-outside-toplevel
|
| 91 |
+
import asyncio # noqa
|
| 92 |
+
from openbb_core.provider.utils.helpers import (
|
| 93 |
+
amake_request,
|
| 94 |
+
safe_fromtimestamp,
|
| 95 |
+
) # noqa
|
| 96 |
+
from pytz import timezone # noqa
|
| 97 |
+
|
| 98 |
+
api_key = credentials.get("tradier_api_key") if credentials else ""
|
| 99 |
+
sandbox = True
|
| 100 |
+
|
| 101 |
+
if api_key and credentials.get("tradier_account_type") not in ["sandbox", "live"]: # type: ignore
|
| 102 |
+
raise OpenBBError(
|
| 103 |
+
"Invalid account type for Tradier. Must be either 'sandbox' or 'live'."
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
if api_key:
|
| 107 |
+
sandbox = (
|
| 108 |
+
credentials.get("tradier_account_type") == "sandbox"
|
| 109 |
+
if credentials
|
| 110 |
+
else False
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
BASE_URL = (
|
| 114 |
+
"https://api.tradier.com/"
|
| 115 |
+
if sandbox is False
|
| 116 |
+
else "https://sandbox.tradier.com/"
|
| 117 |
+
)
|
| 118 |
+
HEADERS = {
|
| 119 |
+
"Authorization": f"Bearer {api_key}",
|
| 120 |
+
"Accept": "application/json",
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
session_filter = "all" if query.extended_hours is True else "open"
|
| 124 |
+
interval = INTERVALS_DICT[query.interval]
|
| 125 |
+
end_point = "timesales" if query.interval in ["1m", "5m", "15m"] else "history"
|
| 126 |
+
results = []
|
| 127 |
+
start_time = "09:30" if query.extended_hours is False else "00:00"
|
| 128 |
+
end_time = "16:00" if query.extended_hours is False else "20:00"
|
| 129 |
+
|
| 130 |
+
async def get_one(symbol):
|
| 131 |
+
"""Get data for one symbol."""
|
| 132 |
+
result = []
|
| 133 |
+
|
| 134 |
+
url = (
|
| 135 |
+
f"{BASE_URL}v1/markets/{end_point}?symbol={symbol}&interval={interval}"
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
if query.interval in ["1m", "5m", "15m"]:
|
| 139 |
+
url += (
|
| 140 |
+
f"&start={query.start_date}%20{start_time}" # type: ignore
|
| 141 |
+
f"&end={query.end_date}%20{end_time}&session_filter={session_filter}" # type: ignore
|
| 142 |
+
)
|
| 143 |
+
if query.interval in ["1d", "1W", "1M"]:
|
| 144 |
+
url += f"&start={query.start_date}&end={query.end_date}"
|
| 145 |
+
|
| 146 |
+
data = await amake_request(url, headers=HEADERS)
|
| 147 |
+
|
| 148 |
+
if interval in ["daily", "weekly", "monthly"] and data.get("history"): # type: ignore
|
| 149 |
+
result = data["history"].get("day") # type: ignore
|
| 150 |
+
if len(query.symbol.split(",")) > 1:
|
| 151 |
+
for r in result:
|
| 152 |
+
r["symbol"] = symbol
|
| 153 |
+
|
| 154 |
+
if interval in ["1min", "5min", "15min"] and data.get("series"): # type: ignore
|
| 155 |
+
result = data["series"].get("data") # type: ignore
|
| 156 |
+
for r in result:
|
| 157 |
+
if len(query.symbol.split(",")) > 1:
|
| 158 |
+
r["symbol"] = symbol
|
| 159 |
+
_ = r.pop("time")
|
| 160 |
+
r["timestamp"] = (
|
| 161 |
+
safe_fromtimestamp(r.get("timestamp"))
|
| 162 |
+
.replace(microsecond=0)
|
| 163 |
+
.astimezone(timezone("America/New_York"))
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
if result != []:
|
| 167 |
+
results.extend(result)
|
| 168 |
+
if result == []:
|
| 169 |
+
warn(f"No data found for {symbol}.")
|
| 170 |
+
|
| 171 |
+
symbols = query.symbol.split(",")
|
| 172 |
+
tasks = [get_one(symbol) for symbol in symbols]
|
| 173 |
+
await asyncio.gather(*tasks)
|
| 174 |
+
|
| 175 |
+
if len(results) == 0:
|
| 176 |
+
raise EmptyDataError("No results found.")
|
| 177 |
+
|
| 178 |
+
return results
|
| 179 |
+
|
| 180 |
+
@staticmethod
|
| 181 |
+
def transform_data(
|
| 182 |
+
query: TradierEquityHistoricalQueryParams,
|
| 183 |
+
data: List[Dict],
|
| 184 |
+
**kwargs: Any,
|
| 185 |
+
) -> List[TradierEquityHistoricalData]:
|
| 186 |
+
"""Transform and validate the data."""
|
| 187 |
+
# pylint: disable=import-outside-toplevel
|
| 188 |
+
from pandas import to_datetime
|
| 189 |
+
|
| 190 |
+
interval = "timestamp" if query.interval in ["1m", "5m", "15m"] else "date"
|
| 191 |
+
return [
|
| 192 |
+
TradierEquityHistoricalData.model_validate(d)
|
| 193 |
+
for d in sorted(data, key=lambda x: x[interval])
|
| 194 |
+
if query.start_date <= to_datetime(d[interval]).date() <= query.end_date
|
| 195 |
+
]
|
openbb_platform/providers/tradier/openbb_tradier/models/equity_quote.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tradier Equity Quote Model."""
|
| 2 |
+
|
| 3 |
+
# pylint: disable = unused-argument
|
| 4 |
+
|
| 5 |
+
from datetime import (
|
| 6 |
+
date as dateType,
|
| 7 |
+
datetime,
|
| 8 |
+
)
|
| 9 |
+
from typing import Any, Dict, List, Literal, Optional
|
| 10 |
+
|
| 11 |
+
from openbb_core.app.model.abstract.error import OpenBBError
|
| 12 |
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
| 13 |
+
from openbb_core.provider.standard_models.equity_quote import (
|
| 14 |
+
EquityQuoteData,
|
| 15 |
+
EquityQuoteQueryParams,
|
| 16 |
+
)
|
| 17 |
+
from openbb_core.provider.utils.errors import EmptyDataError
|
| 18 |
+
from openbb_tradier.utils.constants import OPTIONS_EXCHANGES, STOCK_EXCHANGES
|
| 19 |
+
from pydantic import Field, field_validator, model_validator
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class TradierEquityQuoteQueryParams(EquityQuoteQueryParams):
|
| 23 |
+
"""Tradier Equity Quote Query."""
|
| 24 |
+
|
| 25 |
+
__json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class TradierEquityQuoteData(EquityQuoteData):
|
| 29 |
+
"""Tradier Equity Quote Data."""
|
| 30 |
+
|
| 31 |
+
__alias_dict__ = {
|
| 32 |
+
"name": "description",
|
| 33 |
+
"exchange": "exch",
|
| 34 |
+
"asset_type": "type",
|
| 35 |
+
"bid_exchange": "bidexch",
|
| 36 |
+
"bid_size": "bidsize",
|
| 37 |
+
"ask_size": "asksize",
|
| 38 |
+
"ask_exchange": "askexch",
|
| 39 |
+
"last_price": "last",
|
| 40 |
+
"last_timestamp": "trade_date",
|
| 41 |
+
"prev_close": "prevclose",
|
| 42 |
+
"year_high": "week_52_high",
|
| 43 |
+
"year_low": "week_52_low",
|
| 44 |
+
"volume_avg": "average_volume",
|
| 45 |
+
"change_percent": "change_percentage",
|
| 46 |
+
"root_symbol": "root_symbols",
|
| 47 |
+
"orats_final_iv": "smv_vol",
|
| 48 |
+
"greeks_timestamp": "updated_at",
|
| 49 |
+
"bid_timestamp": "bid_date",
|
| 50 |
+
"ask_timestamp": "ask_date",
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
last_volume: Optional[int] = Field(
|
| 54 |
+
default=None,
|
| 55 |
+
description="The last trade volume.",
|
| 56 |
+
)
|
| 57 |
+
volume_avg: Optional[int] = Field(
|
| 58 |
+
default=None,
|
| 59 |
+
description="The average daily trading volume.",
|
| 60 |
+
)
|
| 61 |
+
bid_timestamp: Optional[datetime] = Field(
|
| 62 |
+
default=None,
|
| 63 |
+
description="Timestamp of the bid price.",
|
| 64 |
+
)
|
| 65 |
+
ask_timestamp: Optional[datetime] = Field(
|
| 66 |
+
default=None,
|
| 67 |
+
description="Timestamp of the ask price.",
|
| 68 |
+
)
|
| 69 |
+
greeks_timestamp: Optional[datetime] = Field(
|
| 70 |
+
default=None,
|
| 71 |
+
description="Timestamp of the greeks data.",
|
| 72 |
+
)
|
| 73 |
+
underlying: Optional[str] = Field(
|
| 74 |
+
default=None,
|
| 75 |
+
description="The underlying symbol for the option.",
|
| 76 |
+
)
|
| 77 |
+
root_symbol: Optional[str] = Field(
|
| 78 |
+
default=None,
|
| 79 |
+
description="The root symbol for the option.",
|
| 80 |
+
)
|
| 81 |
+
option_type: Optional[Literal["call", "put"]] = Field(
|
| 82 |
+
default=None,
|
| 83 |
+
description="Type of option - call or put.",
|
| 84 |
+
)
|
| 85 |
+
contract_size: Optional[int] = Field(
|
| 86 |
+
default=None,
|
| 87 |
+
description="The number of shares in a standard contract.",
|
| 88 |
+
)
|
| 89 |
+
expiration_type: Optional[str] = Field(
|
| 90 |
+
default=None,
|
| 91 |
+
description="The expiration type of the option - i.e, standard, weekly, etc.",
|
| 92 |
+
)
|
| 93 |
+
expiration_date: Optional[dateType] = Field(
|
| 94 |
+
default=None,
|
| 95 |
+
description="The expiration date of the option.",
|
| 96 |
+
)
|
| 97 |
+
strike: Optional[float] = Field(
|
| 98 |
+
default=None,
|
| 99 |
+
description="The strike price of the option.",
|
| 100 |
+
)
|
| 101 |
+
open_interest: Optional[int] = Field(
|
| 102 |
+
default=None,
|
| 103 |
+
description="The number of open contracts for the option.",
|
| 104 |
+
)
|
| 105 |
+
bid_iv: Optional[float] = Field(
|
| 106 |
+
default=None,
|
| 107 |
+
description="Implied volatility of the bid price.",
|
| 108 |
+
)
|
| 109 |
+
ask_iv: Optional[float] = Field(
|
| 110 |
+
default=None,
|
| 111 |
+
description="Implied volatility of the ask price.",
|
| 112 |
+
)
|
| 113 |
+
mid_iv: Optional[float] = Field(
|
| 114 |
+
default=None,
|
| 115 |
+
description="Mid-point implied volatility of the option.",
|
| 116 |
+
)
|
| 117 |
+
orats_final_iv: Optional[float] = Field(
|
| 118 |
+
default=None,
|
| 119 |
+
description="ORATS final implied volatility of the option.",
|
| 120 |
+
)
|
| 121 |
+
delta: Optional[float] = Field(
|
| 122 |
+
default=None,
|
| 123 |
+
description="Delta of the option.",
|
| 124 |
+
)
|
| 125 |
+
gamma: Optional[float] = Field(
|
| 126 |
+
default=None,
|
| 127 |
+
description="Gamma of the option.",
|
| 128 |
+
)
|
| 129 |
+
theta: Optional[float] = Field(
|
| 130 |
+
default=None,
|
| 131 |
+
description="Theta of the option.",
|
| 132 |
+
)
|
| 133 |
+
vega: Optional[float] = Field(
|
| 134 |
+
default=None,
|
| 135 |
+
description="Vega of the option.",
|
| 136 |
+
)
|
| 137 |
+
rho: Optional[float] = Field(
|
| 138 |
+
default=None,
|
| 139 |
+
description="Rho of the option.",
|
| 140 |
+
)
|
| 141 |
+
phi: Optional[float] = Field(
|
| 142 |
+
default=None,
|
| 143 |
+
description="Phi of the option.",
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
@field_validator(
|
| 147 |
+
"last_timestamp",
|
| 148 |
+
"ask_timestamp",
|
| 149 |
+
"bid_timestamp",
|
| 150 |
+
"greeks_timestamp",
|
| 151 |
+
mode="before",
|
| 152 |
+
check_fields=False,
|
| 153 |
+
)
|
| 154 |
+
@classmethod
|
| 155 |
+
def validate_dates(cls, v):
|
| 156 |
+
"""Validate the dates."""
|
| 157 |
+
# pylint: disable=import-outside-toplevel
|
| 158 |
+
from dateutil.parser import parse
|
| 159 |
+
from openbb_core.provider.utils.helpers import safe_fromtimestamp
|
| 160 |
+
from pytz import timezone
|
| 161 |
+
|
| 162 |
+
if v != 0 and v is not None and isinstance(v, int):
|
| 163 |
+
v = int(v) / 1000 # milliseconds to seconds
|
| 164 |
+
v = safe_fromtimestamp(v)
|
| 165 |
+
v = v.replace(microsecond=0)
|
| 166 |
+
v = v.astimezone(timezone("America/New_York"))
|
| 167 |
+
return v
|
| 168 |
+
if v is not None and isinstance(v, str):
|
| 169 |
+
v = parse(v)
|
| 170 |
+
v = v.replace(microsecond=0, tzinfo=timezone("UTC"))
|
| 171 |
+
v = v.astimezone(timezone("America/New_York"))
|
| 172 |
+
return v
|
| 173 |
+
return None
|
| 174 |
+
|
| 175 |
+
@field_validator("change_percent", mode="before", check_fields=False)
|
| 176 |
+
@classmethod
|
| 177 |
+
def normalize_percent(cls, v):
|
| 178 |
+
"""Normalize the percentage."""
|
| 179 |
+
return float(v) / 100 if v else None
|
| 180 |
+
|
| 181 |
+
@model_validator(mode="before")
|
| 182 |
+
@classmethod
|
| 183 |
+
def replace_zero(cls, values):
|
| 184 |
+
"""Check for zero values and replace with None."""
|
| 185 |
+
return (
|
| 186 |
+
{k: None if (v == 0 or str(v) == "0") else v for k, v in values.items()}
|
| 187 |
+
if isinstance(values, dict)
|
| 188 |
+
else values
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
class TradierEquityQuoteFetcher(
|
| 193 |
+
Fetcher[TradierEquityQuoteQueryParams, List[TradierEquityQuoteData]]
|
| 194 |
+
):
|
| 195 |
+
"""Tradier Equity Quote Fetcher."""
|
| 196 |
+
|
| 197 |
+
@staticmethod
|
| 198 |
+
def transform_query(params: Dict[str, Any]) -> TradierEquityQuoteQueryParams:
|
| 199 |
+
"""Transform the query."""
|
| 200 |
+
return TradierEquityQuoteQueryParams(**params)
|
| 201 |
+
|
| 202 |
+
@staticmethod
|
| 203 |
+
async def aextract_data(
|
| 204 |
+
query: TradierEquityQuoteQueryParams,
|
| 205 |
+
credentials: Optional[Dict[str, str]],
|
| 206 |
+
**kwargs: Any,
|
| 207 |
+
) -> List[Dict]:
|
| 208 |
+
"""Return the raw data from the Tradier endpoint."""
|
| 209 |
+
# pylint: disable=import-outside-toplevel
|
| 210 |
+
from openbb_core.provider.utils.helpers import amake_request
|
| 211 |
+
|
| 212 |
+
api_key = credentials.get("tradier_api_key") if credentials else ""
|
| 213 |
+
sandbox = True
|
| 214 |
+
|
| 215 |
+
if api_key and credentials.get("tradier_account_type") not in ["sandbox", "live"]: # type: ignore
|
| 216 |
+
raise OpenBBError(
|
| 217 |
+
"Invalid account type for Tradier. Must be either 'sandbox' or 'live'."
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
if api_key:
|
| 221 |
+
sandbox = (
|
| 222 |
+
credentials.get("tradier_account_type") == "sandbox"
|
| 223 |
+
if credentials
|
| 224 |
+
else False
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
BASE_URL = (
|
| 228 |
+
"https://api.tradier.com/"
|
| 229 |
+
if sandbox is False
|
| 230 |
+
else "https://sandbox.tradier.com/"
|
| 231 |
+
)
|
| 232 |
+
HEADERS = {
|
| 233 |
+
"Authorization": f"Bearer {api_key}",
|
| 234 |
+
"Accept": "application/json",
|
| 235 |
+
}
|
| 236 |
+
url = f"{BASE_URL}v1/markets/quotes?symbols={query.symbol}&greeks=true"
|
| 237 |
+
|
| 238 |
+
response = await amake_request(url, headers=HEADERS)
|
| 239 |
+
|
| 240 |
+
if response.get("quotes"): # type: ignore
|
| 241 |
+
data = response["quotes"].get("quote") # type: ignore
|
| 242 |
+
if len(data) > 0:
|
| 243 |
+
return data if isinstance(data, list) else [data]
|
| 244 |
+
|
| 245 |
+
raise EmptyDataError("No results found.")
|
| 246 |
+
|
| 247 |
+
@staticmethod
|
| 248 |
+
def transform_data(
|
| 249 |
+
query: TradierEquityQuoteQueryParams,
|
| 250 |
+
data: List[Dict],
|
| 251 |
+
**kwargs: Any,
|
| 252 |
+
) -> List[TradierEquityQuoteData]:
|
| 253 |
+
"""Transform and validate the data."""
|
| 254 |
+
results: List[TradierEquityQuoteData] = []
|
| 255 |
+
|
| 256 |
+
for d in data:
|
| 257 |
+
|
| 258 |
+
d["exch"] = (
|
| 259 |
+
OPTIONS_EXCHANGES.get(d["exch"])
|
| 260 |
+
if d.get("type") in ["option", "index"]
|
| 261 |
+
else STOCK_EXCHANGES.get(d["exch"])
|
| 262 |
+
)
|
| 263 |
+
d["askexch"] = (
|
| 264 |
+
OPTIONS_EXCHANGES.get(d["askexch"])
|
| 265 |
+
if d.get("type") in ["option", "index"]
|
| 266 |
+
else STOCK_EXCHANGES.get(d["askexch"])
|
| 267 |
+
)
|
| 268 |
+
d["bidexch"] = (
|
| 269 |
+
OPTIONS_EXCHANGES.get(d["bidexch"])
|
| 270 |
+
if d.get("type") in ["option", "index"]
|
| 271 |
+
else STOCK_EXCHANGES.get(d["bidexch"])
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
if "greeks" in d:
|
| 275 |
+
# Flatten the nested greeks dictionary
|
| 276 |
+
greeks = d.pop("greeks")
|
| 277 |
+
if greeks is not None:
|
| 278 |
+
d.update(**greeks)
|
| 279 |
+
|
| 280 |
+
if (
|
| 281 |
+
d.get("root_symbols") == d.get("symbol")
|
| 282 |
+
and d.get("root_symbols") is not None
|
| 283 |
+
):
|
| 284 |
+
_ = d.pop("root_symbols")
|
| 285 |
+
|
| 286 |
+
if (
|
| 287 |
+
d.get("root_symbol") == d.get("underlying")
|
| 288 |
+
and d.get("root_symbol") is not None
|
| 289 |
+
):
|
| 290 |
+
_ = d.pop("root_symbol")
|
| 291 |
+
|
| 292 |
+
results.append(TradierEquityQuoteData.model_validate(d))
|
| 293 |
+
|
| 294 |
+
return results
|
openbb_platform/providers/tradier/openbb_tradier/models/equity_search.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tradier Equity Search Model."""
|
| 2 |
+
|
| 3 |
+
# pylint: disable = unused-argument
|
| 4 |
+
|
| 5 |
+
from typing import Any, Dict, List, Literal, Optional
|
| 6 |
+
|
| 7 |
+
from openbb_core.app.model.abstract.error import OpenBBError
|
| 8 |
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
| 9 |
+
from openbb_core.provider.standard_models.equity_search import (
|
| 10 |
+
EquitySearchData,
|
| 11 |
+
EquitySearchQueryParams,
|
| 12 |
+
)
|
| 13 |
+
from openbb_core.provider.utils.errors import EmptyDataError
|
| 14 |
+
from openbb_tradier.utils.constants import OPTIONS_EXCHANGES, STOCK_EXCHANGES
|
| 15 |
+
from pydantic import Field, field_validator
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class TradierEquitySearchQueryParams(EquitySearchQueryParams):
|
| 19 |
+
"""
|
| 20 |
+
Tradier Equity Search Query.
|
| 21 |
+
|
| 22 |
+
The search query string should be the beginning of the name or symbol.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
is_symbol: bool = Field(
|
| 26 |
+
description="Whether the query is a symbol. Defaults to False.",
|
| 27 |
+
default=False,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class TradierEquitySearchData(EquitySearchData):
|
| 32 |
+
"""Tradier Equity Search Data."""
|
| 33 |
+
|
| 34 |
+
__alias_dict__ = {
|
| 35 |
+
"name": "description",
|
| 36 |
+
"security_type": "type",
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
exchange: str = Field(description="Exchange where the security is listed.")
|
| 40 |
+
security_type: Literal["stock", "option", "etf", "index", "mutual_fund"] = Field(
|
| 41 |
+
description="Type of security."
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
@field_validator("name", "exchange", mode="before", check_fields=False)
|
| 45 |
+
@classmethod
|
| 46 |
+
def name_validator(cls, v: str) -> str:
|
| 47 |
+
"""Validate the name."""
|
| 48 |
+
if v is None:
|
| 49 |
+
return "N/A"
|
| 50 |
+
return v
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class TradierEquitySearchFetcher(
|
| 54 |
+
Fetcher[TradierEquitySearchQueryParams, List[TradierEquitySearchData]]
|
| 55 |
+
):
|
| 56 |
+
"""Tradier Equity Search Fetcher."""
|
| 57 |
+
|
| 58 |
+
@staticmethod
|
| 59 |
+
def transform_query(params: Dict[str, Any]) -> TradierEquitySearchQueryParams:
|
| 60 |
+
"""Transform the query."""
|
| 61 |
+
return TradierEquitySearchQueryParams(**params)
|
| 62 |
+
|
| 63 |
+
@staticmethod
|
| 64 |
+
async def aextract_data(
|
| 65 |
+
query: TradierEquitySearchQueryParams,
|
| 66 |
+
credentials: Optional[Dict[str, str]],
|
| 67 |
+
**kwargs: Any,
|
| 68 |
+
) -> List[Dict]:
|
| 69 |
+
"""Return the raw data from the Tradier endpoint."""
|
| 70 |
+
# pylint: disable=import-outside-toplevel
|
| 71 |
+
from openbb_core.provider.utils.helpers import amake_request
|
| 72 |
+
|
| 73 |
+
api_key = credentials.get("tradier_api_key") if credentials else ""
|
| 74 |
+
sandbox = True
|
| 75 |
+
|
| 76 |
+
if api_key and credentials.get("tradier_account_type") not in ["sandbox", "live"]: # type: ignore
|
| 77 |
+
raise OpenBBError(
|
| 78 |
+
"Invalid account type for Tradier. Must be either 'sandbox' or 'live'."
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
if api_key:
|
| 82 |
+
sandbox = (
|
| 83 |
+
credentials.get("tradier_account_type") == "sandbox"
|
| 84 |
+
if credentials
|
| 85 |
+
else False
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
BASE_URL = (
|
| 89 |
+
"https://api.tradier.com/"
|
| 90 |
+
if sandbox is False
|
| 91 |
+
else "https://sandbox.tradier.com/"
|
| 92 |
+
)
|
| 93 |
+
HEADERS = {
|
| 94 |
+
"Authorization": f"Bearer {api_key}",
|
| 95 |
+
"Accept": "application/json",
|
| 96 |
+
}
|
| 97 |
+
is_symbol = "lookup" if query.is_symbol else "search"
|
| 98 |
+
url = f"{BASE_URL}v1/markets/{is_symbol}?q={query.query}"
|
| 99 |
+
if is_symbol == "lookup":
|
| 100 |
+
url += "&types=stock, option, etf, index"
|
| 101 |
+
if is_symbol == "search":
|
| 102 |
+
url += "&indexes=true"
|
| 103 |
+
|
| 104 |
+
response = await amake_request(url, headers=HEADERS)
|
| 105 |
+
|
| 106 |
+
if response.get("securities"): # type: ignore
|
| 107 |
+
data = response["securities"].get("security") # type: ignore
|
| 108 |
+
if len(data) > 0:
|
| 109 |
+
return data if isinstance(data, list) else [data]
|
| 110 |
+
|
| 111 |
+
raise EmptyDataError("No results found.")
|
| 112 |
+
|
| 113 |
+
@staticmethod
|
| 114 |
+
def transform_data(
|
| 115 |
+
query: TradierEquitySearchQueryParams,
|
| 116 |
+
data: List[Dict],
|
| 117 |
+
**kwargs: Any,
|
| 118 |
+
) -> List[TradierEquitySearchData]:
|
| 119 |
+
"""Transform and validate the data."""
|
| 120 |
+
results: List[TradierEquitySearchData] = []
|
| 121 |
+
|
| 122 |
+
for d in data:
|
| 123 |
+
d["exchange"] = (
|
| 124 |
+
OPTIONS_EXCHANGES.get(d["exchange"])
|
| 125 |
+
if d.get("type") in ["option", "index"]
|
| 126 |
+
else STOCK_EXCHANGES.get(d["exchange"])
|
| 127 |
+
)
|
| 128 |
+
results.append(TradierEquitySearchData.model_validate(d))
|
| 129 |
+
|
| 130 |
+
return results
|
openbb_platform/providers/tradier/openbb_tradier/models/options_chains.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tradier Options Chains Model."""
|
| 2 |
+
|
| 3 |
+
# pylint: disable = unused-argument
|
| 4 |
+
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
from typing import Any, Dict, List, Optional, Union
|
| 7 |
+
|
| 8 |
+
from openbb_core.app.model.abstract.error import OpenBBError
|
| 9 |
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
| 10 |
+
from openbb_core.provider.standard_models.options_chains import (
|
| 11 |
+
OptionsChainsData,
|
| 12 |
+
OptionsChainsQueryParams,
|
| 13 |
+
)
|
| 14 |
+
from openbb_core.provider.utils.errors import EmptyDataError
|
| 15 |
+
from openbb_tradier.utils.constants import OPTIONS_EXCHANGES, STOCK_EXCHANGES
|
| 16 |
+
from pydantic import Field
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class TradierOptionsChainsQueryParams(OptionsChainsQueryParams):
|
| 20 |
+
"""Tradier Options Chains Query.
|
| 21 |
+
|
| 22 |
+
Source: https://documentation.tradier.com/brokerage-api/markets/get-options-chains
|
| 23 |
+
|
| 24 |
+
Greeks/IV data is updated once per hour.
|
| 25 |
+
This data is calculated using the ORATS APIs and is supplied directly from them.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class TradierOptionsChainsData(OptionsChainsData):
|
| 30 |
+
"""Tradier Options Chains Data."""
|
| 31 |
+
|
| 32 |
+
__doc__ = OptionsChainsData.__doc__
|
| 33 |
+
__alias_dict__ = {
|
| 34 |
+
"expiration": "expiration_date",
|
| 35 |
+
"underlying_symbol": "underlying",
|
| 36 |
+
"contract_symbol": "symbol",
|
| 37 |
+
"last_trade_price": "last",
|
| 38 |
+
"bid_size": "bidsize",
|
| 39 |
+
"ask_size": "asksize",
|
| 40 |
+
"change_percent": "change_percentage",
|
| 41 |
+
"orats_final_iv": "smv_vol",
|
| 42 |
+
"implied_volatility": "mid_iv",
|
| 43 |
+
"greeks_time": "updated_at",
|
| 44 |
+
"prev_close": "prevclose",
|
| 45 |
+
"year_high": "week_52_high",
|
| 46 |
+
"year_low": "week_52_low",
|
| 47 |
+
"last_trade_time": "trade_date",
|
| 48 |
+
"last_trade_size": "last_volume",
|
| 49 |
+
"ask_exchange": "askexch",
|
| 50 |
+
"ask_time": "ask_date",
|
| 51 |
+
"bid_exchange": "bidexch",
|
| 52 |
+
"bid_time": "bid_date",
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
phi: List[Union[float, None]] = Field(
|
| 56 |
+
default_factory=list,
|
| 57 |
+
description="Phi of the option. The sensitivity of the option relative to dividend yield.",
|
| 58 |
+
)
|
| 59 |
+
bid_iv: List[Union[float, None]] = Field(
|
| 60 |
+
default_factory=list,
|
| 61 |
+
description="Implied volatility of the bid price.",
|
| 62 |
+
)
|
| 63 |
+
ask_iv: List[Union[float, None]] = Field(
|
| 64 |
+
default_factory=list,
|
| 65 |
+
description="Implied volatility of the ask price.",
|
| 66 |
+
)
|
| 67 |
+
orats_final_iv: List[Union[float, None]] = Field(
|
| 68 |
+
default_factory=list,
|
| 69 |
+
description="ORATS final implied volatility of the option, updated once per hour.",
|
| 70 |
+
)
|
| 71 |
+
year_high: List[Union[float, None]] = Field(
|
| 72 |
+
default_factory=list,
|
| 73 |
+
description="52-week high price of the option.",
|
| 74 |
+
)
|
| 75 |
+
year_low: List[Union[float, None]] = Field(
|
| 76 |
+
default_factory=list,
|
| 77 |
+
description="52-week low price of the option.",
|
| 78 |
+
)
|
| 79 |
+
contract_size: List[Union[int, None]] = Field(
|
| 80 |
+
default_factory=list,
|
| 81 |
+
description="Size of the contract.",
|
| 82 |
+
)
|
| 83 |
+
greeks_time: List[Union[datetime, None]] = Field(
|
| 84 |
+
default_factory=list,
|
| 85 |
+
description="Timestamp of the last greeks update."
|
| 86 |
+
+ " Greeks/IV data is updated once per hour.",
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class TradierOptionsChainsFetcher(
|
| 91 |
+
Fetcher[TradierOptionsChainsQueryParams, TradierOptionsChainsData]
|
| 92 |
+
):
|
| 93 |
+
"""Tradier Options Chains Fetcher."""
|
| 94 |
+
|
| 95 |
+
@staticmethod
|
| 96 |
+
def transform_query(params: Dict[str, Any]) -> TradierOptionsChainsQueryParams:
|
| 97 |
+
"""Transform the query parameters."""
|
| 98 |
+
return TradierOptionsChainsQueryParams(**params)
|
| 99 |
+
|
| 100 |
+
@staticmethod
|
| 101 |
+
async def aextract_data(
|
| 102 |
+
query: TradierOptionsChainsQueryParams,
|
| 103 |
+
credentials: Optional[Dict[str, str]],
|
| 104 |
+
**kwargs: Any,
|
| 105 |
+
) -> List[Dict]:
|
| 106 |
+
"""Return the raw data from the Tradier endpoint."""
|
| 107 |
+
# pylint: disable=import-outside-toplevel
|
| 108 |
+
import asyncio # noqa
|
| 109 |
+
from openbb_core.provider.utils.helpers import amake_request # noqa
|
| 110 |
+
from openbb_tradier.models.equity_quote import TradierEquityQuoteFetcher # noqa
|
| 111 |
+
|
| 112 |
+
api_key = credentials.get("tradier_api_key") if credentials else ""
|
| 113 |
+
sandbox = True
|
| 114 |
+
|
| 115 |
+
if api_key and credentials.get("tradier_account_type") not in ["sandbox", "live"]: # type: ignore
|
| 116 |
+
raise OpenBBError(
|
| 117 |
+
"Invalid account type for Tradier. Must be either 'sandbox' or 'live'."
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
if api_key:
|
| 121 |
+
sandbox = (
|
| 122 |
+
credentials.get("tradier_account_type") == "sandbox"
|
| 123 |
+
if credentials
|
| 124 |
+
else False
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
BASE_URL = (
|
| 128 |
+
"https://api.tradier.com/v1/markets/options/"
|
| 129 |
+
if sandbox is False
|
| 130 |
+
else "https://sandbox.tradier.com/v1/markets/options/"
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
HEADERS = {
|
| 134 |
+
"Authorization": f"Bearer {api_key}",
|
| 135 |
+
"Accept": "application/json",
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
# Get the expiration dates for the symbol so we can gather the chains data.
|
| 139 |
+
async def get_expirations(symbol):
|
| 140 |
+
"""Get the expiration dates for the given symbol."""
|
| 141 |
+
url = (
|
| 142 |
+
f"{BASE_URL}expirations?symbol={symbol}&includeAllRoots=true"
|
| 143 |
+
"&strikes=false&contractSize=false&expirationType=false"
|
| 144 |
+
)
|
| 145 |
+
response = await amake_request(url, headers=HEADERS)
|
| 146 |
+
if response.get("expirations") and isinstance(response["expirations"].get("date"), list): # type: ignore
|
| 147 |
+
expirations = response["expirations"].get("date") # type: ignore
|
| 148 |
+
return expirations if expirations else []
|
| 149 |
+
|
| 150 |
+
expirations = await get_expirations(query.symbol)
|
| 151 |
+
if expirations == []:
|
| 152 |
+
raise OpenBBError(f"No expiration dates found for {query.symbol}")
|
| 153 |
+
|
| 154 |
+
results: List = []
|
| 155 |
+
|
| 156 |
+
underlying_quote = await TradierEquityQuoteFetcher.fetch_data(
|
| 157 |
+
{"symbol": query.symbol}, credentials
|
| 158 |
+
)
|
| 159 |
+
underlying_price = underlying_quote[0].last_price # type: ignore
|
| 160 |
+
|
| 161 |
+
async def get_one(url, underlying_price):
|
| 162 |
+
"""Get the chain for a single expiration."""
|
| 163 |
+
chain = await amake_request(url, headers=HEADERS)
|
| 164 |
+
if chain.get("options") and isinstance(chain["options"].get("option", []), list): # type: ignore
|
| 165 |
+
data = chain["options"]["option"] # type: ignore
|
| 166 |
+
for d in data.copy():
|
| 167 |
+
# Remove any strikes returned without data.
|
| 168 |
+
keys = ["last", "bid", "ask"]
|
| 169 |
+
if all(d.get(key) in [0, "0", None] for key in keys):
|
| 170 |
+
data.remove(d)
|
| 171 |
+
continue
|
| 172 |
+
# Flatten the nested greeks dictionary
|
| 173 |
+
greeks = d.pop("greeks")
|
| 174 |
+
if greeks is not None:
|
| 175 |
+
d.update(**greeks)
|
| 176 |
+
# Pop fields that are duplicate information or not of interest.
|
| 177 |
+
to_pop = [
|
| 178 |
+
"root_symbol",
|
| 179 |
+
"exch",
|
| 180 |
+
"type",
|
| 181 |
+
"expiration_type",
|
| 182 |
+
"description",
|
| 183 |
+
"average_volume",
|
| 184 |
+
]
|
| 185 |
+
_ = [d.pop(key) for key in to_pop if key in d]
|
| 186 |
+
# Add the DTE field to the data for easier filtering later.
|
| 187 |
+
d["dte"] = (
|
| 188 |
+
datetime.strptime(d["expiration_date"], "%Y-%m-%d").date()
|
| 189 |
+
- datetime.now().date()
|
| 190 |
+
).days
|
| 191 |
+
if underlying_price is not None:
|
| 192 |
+
d["underlying_price"] = underlying_price
|
| 193 |
+
|
| 194 |
+
results.extend(data)
|
| 195 |
+
|
| 196 |
+
urls = [
|
| 197 |
+
f"{BASE_URL}chains?symbol={query.symbol}&expiration={expiration}&greeks=true"
|
| 198 |
+
for expiration in expirations # type: ignore
|
| 199 |
+
]
|
| 200 |
+
|
| 201 |
+
await asyncio.gather(*[get_one(url, underlying_price) for url in urls])
|
| 202 |
+
|
| 203 |
+
if not results:
|
| 204 |
+
raise EmptyDataError(f"No options chains data found for {query.symbol}.")
|
| 205 |
+
return sorted(
|
| 206 |
+
results, key=lambda x: [x["expiration_date"], x["strike"], x["symbol"]]
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
@staticmethod
|
| 210 |
+
def transform_data(
|
| 211 |
+
query: TradierOptionsChainsQueryParams,
|
| 212 |
+
data: List[Dict],
|
| 213 |
+
**kwargs: Any,
|
| 214 |
+
) -> TradierOptionsChainsData:
|
| 215 |
+
"""Transform and validate the data."""
|
| 216 |
+
# pylint: disable = import-outside-toplevel
|
| 217 |
+
from dateutil.parser import parse
|
| 218 |
+
from numpy import nan
|
| 219 |
+
from openbb_core.provider.utils.helpers import safe_fromtimestamp
|
| 220 |
+
from pandas import DataFrame
|
| 221 |
+
from pytz import timezone
|
| 222 |
+
|
| 223 |
+
def df_apply_dates(v):
|
| 224 |
+
"""Validate the dates."""
|
| 225 |
+
if v != 0 and v is not None and isinstance(v, int):
|
| 226 |
+
v = int(v) / 1000 # milliseconds to seconds
|
| 227 |
+
v = safe_fromtimestamp(v)
|
| 228 |
+
v = v.replace(microsecond=0)
|
| 229 |
+
v = v.astimezone(timezone("America/New_York"))
|
| 230 |
+
return v
|
| 231 |
+
if v is not None and isinstance(v, str):
|
| 232 |
+
v = parse(v)
|
| 233 |
+
v = v.replace(microsecond=0, tzinfo=timezone("UTC"))
|
| 234 |
+
v = v.astimezone(timezone("America/New_York"))
|
| 235 |
+
return v
|
| 236 |
+
return None
|
| 237 |
+
|
| 238 |
+
def map_exchange(v):
|
| 239 |
+
"""Map the exchange from a code to a name."""
|
| 240 |
+
return (
|
| 241 |
+
OPTIONS_EXCHANGES.get(v)
|
| 242 |
+
if v in OPTIONS_EXCHANGES
|
| 243 |
+
else (
|
| 244 |
+
STOCK_EXCHANGES.get(v) if v in STOCK_EXCHANGES else v if v else None
|
| 245 |
+
)
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
output = DataFrame(data)
|
| 249 |
+
for col in output:
|
| 250 |
+
if col not in ["dte", "open_interest", "volume"]:
|
| 251 |
+
output[col] = output[col].replace({0: None})
|
| 252 |
+
elif col in ["bid_date", "ask_date", "trade_date", "updated_at"]:
|
| 253 |
+
output[col] = output[col].apply(df_apply_dates)
|
| 254 |
+
elif col == "change_percentage":
|
| 255 |
+
output[col] = [float(d) / 100 if d else None for d in output[col]]
|
| 256 |
+
elif col in ["bidexch", "askexch"]:
|
| 257 |
+
output[col] = output[col].apply(map_exchange)
|
| 258 |
+
else:
|
| 259 |
+
continue
|
| 260 |
+
|
| 261 |
+
output = output.replace({nan: None}).dropna(how="all", axis=1)
|
| 262 |
+
|
| 263 |
+
return TradierOptionsChainsData.model_validate(output.to_dict(orient="list"))
|
openbb_platform/providers/tradier/openbb_tradier/py.typed
ADDED
|
File without changes
|
openbb_platform/providers/tradier/openbb_tradier/utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Tradier provider utils."""
|
openbb_platform/providers/tradier/openbb_tradier/utils/constants.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tradier Provider Constants"""
|
| 2 |
+
|
| 3 |
+
STOCK_EXCHANGES = {
|
| 4 |
+
"A": "NYSE MKT",
|
| 5 |
+
"B": "NASDAQ OMX BX",
|
| 6 |
+
"C": "National Stock Exchange",
|
| 7 |
+
"D": "FINRA ADF",
|
| 8 |
+
"E": "Market Independent (Generated by Nasdaq SIP)",
|
| 9 |
+
"F": "Mutual Funds/Money Markets (NASDAQ)",
|
| 10 |
+
"I": "International Securities Exchange",
|
| 11 |
+
"J": "Direct Edge A",
|
| 12 |
+
"K": "Direct Edge X",
|
| 13 |
+
"L": "Long Term Stock Exchange",
|
| 14 |
+
"M": "Chicago Stock Exchange",
|
| 15 |
+
"N": "NYSE",
|
| 16 |
+
"P": "NYSE Arca",
|
| 17 |
+
"Q": "NASDAQ OMX",
|
| 18 |
+
"S": "NASDAQ Small Cap",
|
| 19 |
+
"T": "NASDAQ Int",
|
| 20 |
+
"U": "OTCBB",
|
| 21 |
+
"V": "OTC other",
|
| 22 |
+
"W": "CBOE",
|
| 23 |
+
"X": "NASDAQ OMX PSX",
|
| 24 |
+
"G": "GLOBEX",
|
| 25 |
+
"Y": "BATS Y-Exchange",
|
| 26 |
+
"Z": "BATS",
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
OPTIONS_EXCHANGES = {
|
| 30 |
+
"A": "NYSE Amex Options",
|
| 31 |
+
"B": "BOX Options Exchange",
|
| 32 |
+
"C": "Chicago Board Options Exchange (CBOE)",
|
| 33 |
+
"H": "ISE Gemini",
|
| 34 |
+
"I": "International Securities Exchange (ISE)",
|
| 35 |
+
"M": "MIAX Options Exchange",
|
| 36 |
+
"N": "NYSE Arca Options",
|
| 37 |
+
"O": "Options Price Reporting Authority (OPRA)",
|
| 38 |
+
"P": "MIAX PEARL",
|
| 39 |
+
"Q": "NASDAQ Options Market",
|
| 40 |
+
"T": "NASDAQ OMX BX",
|
| 41 |
+
"W": "C2 Options Exchange",
|
| 42 |
+
"X": "NASDAQ OMX PHLX",
|
| 43 |
+
"Z": "BATS Options Market",
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
INTERVALS_DICT = {
|
| 47 |
+
"1m": "1min",
|
| 48 |
+
"5m": "5min",
|
| 49 |
+
"15m": "15min",
|
| 50 |
+
"1d": "daily",
|
| 51 |
+
"1W": "weekly",
|
| 52 |
+
"1M": "monthly",
|
| 53 |
+
}
|
openbb_platform/providers/tradier/poetry.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
openbb_platform/providers/tradier/pyproject.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[tool.poetry]
|
| 2 |
+
name = "openbb-tradier"
|
| 3 |
+
version = "1.3.1"
|
| 4 |
+
description = "Tradier Provider Extension for the OpenBB Platform"
|
| 5 |
+
authors = ["OpenBB <hello@openbb.co>"]
|
| 6 |
+
license = "AGPL-3.0-only"
|
| 7 |
+
readme = "README.md"
|
| 8 |
+
packages = [{ include = "openbb_tradier" }]
|
| 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_provider_extension"]
|
| 19 |
+
tradier = "openbb_tradier:tradier_provider"
|
openbb_platform/providers/tradier/tests/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Tradier provider tests."""
|
openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_derivatives_options_chains_fetcher_urllib3_v1.yaml
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_derivatives_options_chains_fetcher_urllib3_v2.yaml
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_historical_fetcher_urllib3_v1.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interactions:
|
| 2 |
+
- request:
|
| 3 |
+
body: null
|
| 4 |
+
headers:
|
| 5 |
+
Accept:
|
| 6 |
+
- application/json
|
| 7 |
+
Authorization:
|
| 8 |
+
- MOCK_API_KEY
|
| 9 |
+
method: GET
|
| 10 |
+
uri: https://sandbox.tradier.com/v1/markets/history?symbol=AAPL&interval=daily&start=2024-02-01&end=2024-02-29
|
| 11 |
+
response:
|
| 12 |
+
body:
|
| 13 |
+
string: '{"history":{"day":[{"date":"2024-02-01","open":183.985,"high":186.95,"low":183.82,"close":186.86,"volume":64885408},{"date":"2024-02-02","open":179.86,"high":187.33,"low":179.25,"close":185.85,"volume":102551680},{"date":"2024-02-05","open":188.15,"high":189.25,"low":185.84,"close":187.68,"volume":69668820},{"date":"2024-02-06","open":186.86,"high":189.31,"low":186.7695,"close":189.3,"volume":43490759},{"date":"2024-02-07","open":190.64,"high":191.05,"low":188.61,"close":189.41,"volume":53438955},{"date":"2024-02-08","open":189.385,"high":189.535,"low":187.35,"close":188.32,"volume":40962046},{"date":"2024-02-09","open":188.65,"high":189.99,"low":188.0,"close":188.85,"volume":45155216},{"date":"2024-02-12","open":188.415,"high":188.67,"low":186.79,"close":187.15,"volume":41781934},{"date":"2024-02-13","open":185.77,"high":186.21,"low":183.5128,"close":185.04,"volume":56529529},{"date":"2024-02-14","open":185.32,"high":185.53,"low":182.44,"close":184.15,"volume":54630517},{"date":"2024-02-15","open":183.55,"high":184.49,"low":181.35,"close":183.86,"volume":65434496},{"date":"2024-02-16","open":183.42,"high":184.85,"low":181.665,"close":182.31,"volume":49752465},{"date":"2024-02-20","open":181.79,"high":182.43,"low":180.0,"close":181.56,"volume":53665553},{"date":"2024-02-21","open":181.94,"high":182.8888,"low":180.66,"close":182.32,"volume":41529674},{"date":"2024-02-22","open":183.48,"high":184.955,"low":182.46,"close":184.37,"volume":52292208},{"date":"2024-02-23","open":185.01,"high":185.04,"low":182.23,"close":182.52,"volume":45119677},{"date":"2024-02-26","open":182.24,"high":182.76,"low":180.65,"close":181.16,"volume":40867421},{"date":"2024-02-27","open":181.1,"high":183.9225,"low":179.56,"close":182.63,"volume":54318851},{"date":"2024-02-28","open":182.51,"high":183.12,"low":180.13,"close":181.42,"volume":48953939},{"date":"2024-02-29","open":181.27,"high":182.57,"low":179.53,"close":180.75,"volume":136682600}]}}'
|
| 14 |
+
headers:
|
| 15 |
+
Access-Control-Allow-Headers:
|
| 16 |
+
- accept, authorization
|
| 17 |
+
Access-Control-Allow-Methods:
|
| 18 |
+
- GET, PUT, POST, DELETE
|
| 19 |
+
Access-Control-Allow-Origin:
|
| 20 |
+
- '*'
|
| 21 |
+
Connection:
|
| 22 |
+
- keep-alive
|
| 23 |
+
Content-Type:
|
| 24 |
+
- application/json;charset=UTF-8
|
| 25 |
+
Date:
|
| 26 |
+
- Sat, 09 Mar 2024 16:46:57 GMT
|
| 27 |
+
Transfer-Encoding:
|
| 28 |
+
- chunked
|
| 29 |
+
X-Ratelimit-Allowed:
|
| 30 |
+
- '200'
|
| 31 |
+
X-Ratelimit-Available:
|
| 32 |
+
- '199'
|
| 33 |
+
X-Ratelimit-Expiry:
|
| 34 |
+
- '1710002820000'
|
| 35 |
+
X-Ratelimit-Used:
|
| 36 |
+
- '1'
|
| 37 |
+
status:
|
| 38 |
+
code: 200
|
| 39 |
+
message: ''
|
| 40 |
+
version: 1
|
openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_historical_fetcher_urllib3_v2.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interactions:
|
| 2 |
+
- request:
|
| 3 |
+
body: null
|
| 4 |
+
headers:
|
| 5 |
+
Accept:
|
| 6 |
+
- application/json
|
| 7 |
+
Authorization:
|
| 8 |
+
- MOCK_API_KEY
|
| 9 |
+
method: GET
|
| 10 |
+
uri: https://sandbox.tradier.com/v1/markets/history?symbol=AAPL&interval=daily&start=2024-02-01&end=2024-02-29
|
| 11 |
+
response:
|
| 12 |
+
body:
|
| 13 |
+
string: '{"history":{"day":[{"date":"2024-02-01","open":183.985,"high":186.95,"low":183.82,"close":186.86,"volume":64885408},{"date":"2024-02-02","open":179.86,"high":187.33,"low":179.25,"close":185.85,"volume":102551680},{"date":"2024-02-05","open":188.15,"high":189.25,"low":185.84,"close":187.68,"volume":69668820},{"date":"2024-02-06","open":186.86,"high":189.31,"low":186.7695,"close":189.3,"volume":43490759},{"date":"2024-02-07","open":190.64,"high":191.05,"low":188.61,"close":189.41,"volume":53438955},{"date":"2024-02-08","open":189.385,"high":189.535,"low":187.35,"close":188.32,"volume":40962046},{"date":"2024-02-09","open":188.65,"high":189.99,"low":188.0,"close":188.85,"volume":45155216},{"date":"2024-02-12","open":188.415,"high":188.67,"low":186.79,"close":187.15,"volume":41781934},{"date":"2024-02-13","open":185.77,"high":186.21,"low":183.5128,"close":185.04,"volume":56529529},{"date":"2024-02-14","open":185.32,"high":185.53,"low":182.44,"close":184.15,"volume":54630517},{"date":"2024-02-15","open":183.55,"high":184.49,"low":181.35,"close":183.86,"volume":65434496},{"date":"2024-02-16","open":183.42,"high":184.85,"low":181.665,"close":182.31,"volume":49752465},{"date":"2024-02-20","open":181.79,"high":182.43,"low":180.0,"close":181.56,"volume":53665553},{"date":"2024-02-21","open":181.94,"high":182.8888,"low":180.66,"close":182.32,"volume":41529674},{"date":"2024-02-22","open":183.48,"high":184.955,"low":182.46,"close":184.37,"volume":52292208},{"date":"2024-02-23","open":185.01,"high":185.04,"low":182.23,"close":182.52,"volume":45119677},{"date":"2024-02-26","open":182.24,"high":182.76,"low":180.65,"close":181.16,"volume":40867421},{"date":"2024-02-27","open":181.1,"high":183.9225,"low":179.56,"close":182.63,"volume":54318851},{"date":"2024-02-28","open":182.51,"high":183.12,"low":180.13,"close":181.42,"volume":48953939},{"date":"2024-02-29","open":181.27,"high":182.57,"low":179.53,"close":180.75,"volume":136682600}]}}'
|
| 14 |
+
headers:
|
| 15 |
+
Access-Control-Allow-Headers:
|
| 16 |
+
- accept, authorization
|
| 17 |
+
Access-Control-Allow-Methods:
|
| 18 |
+
- GET, PUT, POST, DELETE
|
| 19 |
+
Access-Control-Allow-Origin:
|
| 20 |
+
- '*'
|
| 21 |
+
Connection:
|
| 22 |
+
- keep-alive
|
| 23 |
+
Content-Type:
|
| 24 |
+
- application/json;charset=UTF-8
|
| 25 |
+
Date:
|
| 26 |
+
- Fri, 28 Jun 2024 16:06:00 GMT
|
| 27 |
+
Transfer-Encoding:
|
| 28 |
+
- chunked
|
| 29 |
+
X-Ratelimit-Allowed:
|
| 30 |
+
- '200'
|
| 31 |
+
X-Ratelimit-Available:
|
| 32 |
+
- '199'
|
| 33 |
+
X-Ratelimit-Expiry:
|
| 34 |
+
- '1719590820000'
|
| 35 |
+
X-Ratelimit-Used:
|
| 36 |
+
- '1'
|
| 37 |
+
status:
|
| 38 |
+
code: 200
|
| 39 |
+
message: ''
|
| 40 |
+
version: 1
|
openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_quote_fetcher_urllib3_v1.yaml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interactions:
|
| 2 |
+
- request:
|
| 3 |
+
body: null
|
| 4 |
+
headers:
|
| 5 |
+
Accept:
|
| 6 |
+
- application/json
|
| 7 |
+
Authorization:
|
| 8 |
+
- MOCK_API_KEY
|
| 9 |
+
method: GET
|
| 10 |
+
uri: https://sandbox.tradier.com/v1/markets/quotes?symbols=SPY,SPY251219P00450000&greeks=true
|
| 11 |
+
response:
|
| 12 |
+
body:
|
| 13 |
+
string: '{"quotes":{"quote":[{"symbol":"SPY","description":"SPDR S&P 500","exch":"P","type":"etf","last":511.72,"change":-3.09,"volume":86532535,"open":515.46,"high":518.2201,"low":511.13,"close":511.72,"bid":511.61,"ask":511.7,"change_percentage":-0.60,"average_volume":77297548,"last_volume":0,"trade_date":1709946000001,"prevclose":514.81,"week_52_high":518.2201,"week_52_low":380.65,"bidsize":4,"bidexch":"P","bid_date":1709945993000,"asksize":2,"askexch":"P","ask_date":1709946000000,"root_symbols":"SPY"},{"symbol":"SPY251219P00450000","description":"SPY
|
| 14 |
+
Dec 19 2025 $450.00 Put","exch":"Z","type":"option","last":18.2,"change":0.90,"volume":5,"open":16.92,"high":18.2,"low":16.92,"close":18.2,"bid":17.5,"ask":18.25,"underlying":"SPY","strike":450.0,"greeks":{"delta":-0.2103079110494788,"gamma":0.0018952361980113632,"theta":-0.05823261496893023,"vega":1.831560584347291,"rho":5.2858929563425905,"phi":-7.218291844537318,"bid_iv":0.204439,"mid_iv":0.2057,"ask_iv":0.206962,"smv_vol":0.202,"updated_at":"2024-03-08
|
| 15 |
+
20:59:41"},"change_percentage":5.21,"average_volume":0,"last_volume":1,"trade_date":1709924339478,"prevclose":17.3,"week_52_high":0.0,"week_52_low":0.0,"bidsize":613,"bidexch":"X","bid_date":1709932499000,"asksize":578,"askexch":"X","ask_date":1709932499000,"open_interest":8081,"contract_size":100,"expiration_date":"2025-12-19","expiration_type":"standard","option_type":"put","root_symbol":"SPY"}]}}'
|
| 16 |
+
headers:
|
| 17 |
+
Access-Control-Allow-Headers:
|
| 18 |
+
- accept, authorization
|
| 19 |
+
Access-Control-Allow-Methods:
|
| 20 |
+
- GET, PUT, POST, DELETE
|
| 21 |
+
Access-Control-Allow-Origin:
|
| 22 |
+
- '*'
|
| 23 |
+
Connection:
|
| 24 |
+
- keep-alive
|
| 25 |
+
Content-Type:
|
| 26 |
+
- application/json;charset=UTF-8
|
| 27 |
+
Date:
|
| 28 |
+
- Sat, 09 Mar 2024 16:46:58 GMT
|
| 29 |
+
Transfer-Encoding:
|
| 30 |
+
- chunked
|
| 31 |
+
X-Ratelimit-Allowed:
|
| 32 |
+
- '200'
|
| 33 |
+
X-Ratelimit-Available:
|
| 34 |
+
- '199'
|
| 35 |
+
X-Ratelimit-Expiry:
|
| 36 |
+
- '1710002820000'
|
| 37 |
+
X-Ratelimit-Used:
|
| 38 |
+
- '1'
|
| 39 |
+
status:
|
| 40 |
+
code: 200
|
| 41 |
+
message: ''
|
| 42 |
+
version: 1
|
openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_quote_fetcher_urllib3_v2.yaml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interactions:
|
| 2 |
+
- request:
|
| 3 |
+
body: null
|
| 4 |
+
headers:
|
| 5 |
+
Accept:
|
| 6 |
+
- application/json
|
| 7 |
+
Authorization:
|
| 8 |
+
- MOCK_API_KEY
|
| 9 |
+
method: GET
|
| 10 |
+
uri: https://sandbox.tradier.com/v1/markets/quotes?symbols=SPY,SPY251219P00450000&greeks=true
|
| 11 |
+
response:
|
| 12 |
+
body:
|
| 13 |
+
string: '{"quotes":{"quote":[{"symbol":"SPY","description":"SPDR S&P 500","exch":"P","type":"etf","last":547.94,"change":1.57,"volume":16616657,"open":547.16,"high":550.28,"low":546.67,"close":null,"bid":547.95,"ask":547.96,"change_percentage":0.29,"average_volume":57774558,"last_volume":100,"trade_date":1719589860990,"prevclose":546.37,"week_52_high":550.12,"week_52_low":409.21,"bidsize":2,"bidexch":"P","bid_date":1719589861000,"asksize":2,"askexch":"Q","ask_date":1719589860000,"root_symbols":"SPY"},{"symbol":"SPY251219P00450000","description":"SPY
|
| 14 |
+
Dec 19 2025 $450.00 Put","exch":"Z","type":"option","last":10.05,"change":-0.49,"volume":415,"open":10.1,"high":10.1,"low":10.05,"close":null,"bid":10.2,"ask":10.36,"underlying":"SPY","strike":450.0,"greeks":{"delta":-0.136692803567622,"gamma":0.0014474030220688475,"theta":-0.05979034211472878,"vega":1.2675943936834042,"rho":5.051298572174882,"phi":-7.01492474930925,"bid_iv":0.208662,"mid_iv":0.209179,"ask_iv":0.209696,"smv_vol":0.211,"updated_at":"2024-06-28
|
| 15 |
+
14:58:47"},"change_percentage":-4.65,"average_volume":0,"last_volume":400,"trade_date":1719585288310,"prevclose":10.54,"week_52_high":0.0,"week_52_low":0.0,"bidsize":251,"bidexch":"Z","bid_date":1719589851000,"asksize":939,"askexch":"N","ask_date":1719589851000,"open_interest":7230,"contract_size":100,"expiration_date":"2025-12-19","expiration_type":"standard","option_type":"put","root_symbol":"SPY"}]}}'
|
| 16 |
+
headers:
|
| 17 |
+
Access-Control-Allow-Headers:
|
| 18 |
+
- accept, authorization
|
| 19 |
+
Access-Control-Allow-Methods:
|
| 20 |
+
- GET, PUT, POST, DELETE
|
| 21 |
+
Access-Control-Allow-Origin:
|
| 22 |
+
- '*'
|
| 23 |
+
Connection:
|
| 24 |
+
- keep-alive
|
| 25 |
+
Content-Type:
|
| 26 |
+
- application/json;charset=UTF-8
|
| 27 |
+
Date:
|
| 28 |
+
- Fri, 28 Jun 2024 16:06:01 GMT
|
| 29 |
+
Transfer-Encoding:
|
| 30 |
+
- chunked
|
| 31 |
+
X-Ratelimit-Allowed:
|
| 32 |
+
- '200'
|
| 33 |
+
X-Ratelimit-Available:
|
| 34 |
+
- '199'
|
| 35 |
+
X-Ratelimit-Expiry:
|
| 36 |
+
- '1719590820000'
|
| 37 |
+
X-Ratelimit-Used:
|
| 38 |
+
- '1'
|
| 39 |
+
status:
|
| 40 |
+
code: 200
|
| 41 |
+
message: ''
|
| 42 |
+
version: 1
|
openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_search_fetcher_urllib3_v1.yaml
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interactions:
|
| 2 |
+
- request:
|
| 3 |
+
body: null
|
| 4 |
+
headers:
|
| 5 |
+
Accept:
|
| 6 |
+
- application/json
|
| 7 |
+
Authorization:
|
| 8 |
+
- MOCK_API_KEY
|
| 9 |
+
method: GET
|
| 10 |
+
uri: https://sandbox.tradier.com/v1/markets/search?q=brookfield&indexes=true
|
| 11 |
+
response:
|
| 12 |
+
body:
|
| 13 |
+
string: '{"securities":{"security":[{"symbol":"BPY","exchange":"Q","type":"stock","description":"Brookfield
|
| 14 |
+
Property Partners LP"},{"symbol":"BN","exchange":"N","type":"stock","description":"Brookfield
|
| 15 |
+
Corporation Class A Limited Voting Shares"},{"symbol":"BAM","exchange":"N","type":"stock","description":"Brookfield
|
| 16 |
+
Asset Management Inc Class A Limited Voting Shares"},{"symbol":"BPR","exchange":"Q","type":"stock","description":"Brookfield
|
| 17 |
+
Property REIT Inc"},{"symbol":"BEPC","exchange":"N","type":"stock","description":"Brookfield
|
| 18 |
+
Renewable Corporation Class A Subordinate Voting Shares"},{"symbol":"BPYU","exchange":"Q","type":"stock","description":"Brookfield
|
| 19 |
+
Property REIT Inc Cl A"},{"symbol":"BIP","exchange":"N","type":"stock","description":"Brookfield
|
| 20 |
+
Infrastructure Partners LP Limited Partnership Units"},{"symbol":"BIPC","exchange":"N","type":"stock","description":"Brookfield
|
| 21 |
+
Infrastructure Corporation"},{"symbol":"BEP","exchange":"N","type":"stock","description":"Brookfield
|
| 22 |
+
Renewable Partners L.P."},{"symbol":"BPYPM","exchange":"Q","type":"stock","description":"Brookfield
|
| 23 |
+
Property Partners L.P. - 6.25% Class A Cumulative Redeemable Preferred Units,
|
| 24 |
+
Series 1"},{"symbol":"BPYPO","exchange":"Q","type":"stock","description":"Brookfield
|
| 25 |
+
Property Partners L.P. - 6.375% Class A Cumulative Redeemable Perpetual Preferred
|
| 26 |
+
Units, Series 2"},{"symbol":"BPYUP","exchange":"Q","type":"stock","description":"Brookfield
|
| 27 |
+
Property REIT Inc 6 3\/8 % Cum Red Pfd Shs Series -A-"},{"symbol":"BBUC","exchange":"N","type":"stock","description":"Brookfield
|
| 28 |
+
Business Corporation Class A Exchangeable Subordinate Voting Shares"},{"symbol":"BPYPP","exchange":"Q","type":"stock","description":"Brookfield
|
| 29 |
+
Property Partners L.P. - 6.50% Class A Cumulative Redeemable Perpetual Preferred
|
| 30 |
+
Units"},{"symbol":"BPYPN","exchange":"Q","type":"stock","description":"Brookfield
|
| 31 |
+
Property Partners L.P. - 5.750% Class A Cumulative Redeemable Perpetual Preferred
|
| 32 |
+
Units, Series 3"},{"symbol":"BEPH","exchange":"N","type":"stock","description":"Brookfield
|
| 33 |
+
BRP Holdings (Canada) Inc. 4.625% Perpetual Subordinated Notes"},{"symbol":"BNH","exchange":"N","type":"stock","description":"Brookfield
|
| 34 |
+
Finance Inc. 4.625% Subordinated Notes due October 16, 2080"},{"symbol":"BNJ","exchange":"N","type":"stock","description":"Brookfield
|
| 35 |
+
Finance Inc. 4.50% Perpetual Subordinated Notes"},{"symbol":"BEPI","exchange":"N","type":"stock","description":"Brookfield
|
| 36 |
+
BRP Holdings (Canada) Inc. 4.875% Perpetual Subordinated Notes"},{"symbol":"BAMH","exchange":"N","type":"stock","description":"Brookfield
|
| 37 |
+
Finance Inc. 4.625% Subordinated Notes due October 16, 2080"},{"symbol":"BAMI","exchange":"N","type":"stock","description":"Brookfield
|
| 38 |
+
Finance Inc. 4.50% Perpetual Subordinated Notes"},{"symbol":"BAMR","exchange":"N","type":"stock","description":"Brookfield
|
| 39 |
+
Asset Management Reinsurance Partners Ltd. Class A Exchangeable Limited Voting
|
| 40 |
+
Shares"},{"symbol":"BBU","exchange":"N","type":"stock","description":"Brookfield
|
| 41 |
+
Business Partners L.P. Limited Partnership Units"},{"symbol":"BIPH","exchange":"N","type":"stock","description":"Brookfield
|
| 42 |
+
Infrastructure Corporation 5.000% Subordinated Notes due 2081"},{"symbol":"BNRE","exchange":"N","type":"stock","description":"Brookfield
|
| 43 |
+
Reinsurance Ltd. Class A Exchangeable Limited Voting Shares"},{"symbol":"BNRE\/A","exchange":"N","type":"stock","description":"Brookfield
|
| 44 |
+
Reinsurance Ltd. Class A-1 Exchangeable Non-Voting Shares"},{"symbol":"BKFAF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 45 |
+
CORP A 2 by BROOKFIELD CORP."},{"symbol":"BAMGF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 46 |
+
CORP A 24 by BROOKFIELD CORP."},{"symbol":"BKAMF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 47 |
+
CORP PFD A 30 by BROOKFIELD CORP."},{"symbol":"BROXF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 48 |
+
CORP PFD A 37 by BROOKFIELD CORP."},{"symbol":"BAMKF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 49 |
+
CORP PREF 26 by BROOKFIELD CORP."},{"symbol":"BXDIF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 50 |
+
CORP PREF A 13 by BROOKFIELD CORP."},{"symbol":"BKFOF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 51 |
+
CP PREF A 34 by BROOKFIELD CORP."},{"symbol":"BKFDF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 52 |
+
CP PREF A 40 by BROOKFIELD CORP."},{"symbol":"BRCFF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 53 |
+
CP PREF A 42 by BROOKFIELD CORP."},{"symbol":"BKFPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 54 |
+
CP PREF A 51 by BROOKFIELD CORP."},{"symbol":"BRFPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 55 |
+
CP SR 32 PRF A by BROOKFIELD CORP."},{"symbol":"DTLAP","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 56 |
+
DTLA 7.625A PR by Brookfield DTLA Inc."},{"symbol":"BRIPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 57 |
+
INF PNR PFD A by Brookfield Infrastructure Partners L.P."},{"symbol":"BKOFF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 58 |
+
OFCE PR II AAA by Brookfield Office Properties Inc."},{"symbol":"BOPPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 59 |
+
OFF PRF SER CC by Brookfield Office Properties Inc."},{"symbol":"BKAAF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 60 |
+
OFF PTY AAA N by Brookfield Office Properties Inc."},{"symbol":"BRKNF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 61 |
+
OFF PTY PREF Y by Brookfield Office Properties Inc."},{"symbol":"BRKFF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 62 |
+
OFFCE PREF AAA by Brookfield Office Properties Inc."},{"symbol":"BRPYF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 63 |
+
OFFCE PROP W by Brookfield Office Properties Inc."},{"symbol":"BRPPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 64 |
+
OFFIE PPTY AAA by Brookfield Office Properties Inc."},{"symbol":"BROPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 65 |
+
PFD A SER 23 by Brookfield Office Properties Inc."},{"symbol":"BPSPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 66 |
+
PTY SPLT PFD 2 by BROOKFIELD PPTY SPLIT CORP."},{"symbol":"BRENF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 67 |
+
RENWBE PFD UTS by Brookfield Renewable Partners L.P."},{"symbol":"BKFTF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 68 |
+
RENWBLE A SR 3 by BROOKFIELD RENEWABLE PWR PFD EQUITY INC"},{"symbol":"BAMW","exchange":"N","type":"stock","description":"Brookfield
|
| 69 |
+
Asset Management Inc Class A Limited Voting Shares When-Issued"},{"symbol":"BMKAF","exchange":"V","type":"stock","description":"Brookfield
|
| 70 |
+
Asset Management Inc Floating Rate Cum Pfd Registered Shs -A- Series -25-"},{"symbol":"BNW","exchange":"N","type":"stock","description":"Brookfield
|
| 71 |
+
Corporation Class A Limited Voting Shares When-Issued"},{"symbol":"DTLA\/WD","exchange":"N","type":"stock","description":"Brookfield
|
| 72 |
+
DTLA Inc. 7.625% Series A Cumulative Redeemable Preferred Stock"},{"symbol":"INF","exchange":"N","type":"stock","description":"Brookfield
|
| 73 |
+
Global Listed Infrastructure Income Fund Inc"},{"symbol":"BIPCW","exchange":"N","type":"stock","description":"Brookfield
|
| 74 |
+
Infrastructure Corp Ordinary Shares - Class A (Subordinate Share)"},{"symbol":"BIPFF","exchange":"V","type":"stock","description":"Brookfield
|
| 75 |
+
Infrastructure Partners LP 5% PRF PERPETUAL USD 25 - Ser 14 Cls A"},{"symbol":"BIP''B","exchange":"N","type":"stock","description":"Brookfield
|
| 76 |
+
Infrastructure Partners LP 5.000% Class A Preferred Limited Partnership Units,
|
| 77 |
+
Series 14"},{"symbol":"BIP\/WDB","exchange":"N","type":"stock","description":"Brookfield
|
| 78 |
+
Infrastructure Partners LP 5.000% Class A Preferred Limited Partnership Units,
|
| 79 |
+
Series 14"},{"symbol":"BIP''A","exchange":"N","type":"stock","description":"Brookfield
|
| 80 |
+
Infrastructure Partners LP 5.125% Class A Preferred Limited Partnership Units,
|
| 81 |
+
Series 13"},{"symbol":"BIP\/WDA","exchange":"N","type":"stock","description":"Brookfield
|
| 82 |
+
Infrastructure Partners LP 5.125% Class A Preferred Limited Partnership Units,
|
| 83 |
+
Series 13"},{"symbol":"BIPPF","exchange":"V","type":"stock","description":"Brookfield
|
| 84 |
+
Infrastructure Partners LP 5.125% PRF PERPETUAL USD 25 - Ser 13 Cls A"},{"symbol":"BIPAF","exchange":"V","type":"stock","description":"Brookfield
|
| 85 |
+
Infrastructure Partners LP Cum Red Pfd Partnership Units -A- Series -5-"},{"symbol":"BINFF","exchange":"V","type":"stock","description":"Brookfield
|
| 86 |
+
Infrastructure Partners LP Cum Red Pfd Partnership Units -A- Series -7-"},{"symbol":"BOPTP","exchange":"V","type":"stock","description":"Brookfield
|
| 87 |
+
Property Partners LP 5.75% PRF PERPETUAL USD 25 - Cls A Ser 3"},{"symbol":"BKOYF","exchange":"V","type":"stock","description":"Brookfield
|
| 88 |
+
Property Partners LP 6.375% PRF PERPETUAL USD 25 - Cls A Ser 2"},{"symbol":"BRKPF","exchange":"V","type":"stock","description":"Brookfield
|
| 89 |
+
Property Partners LP Cls A Ser 1"},{"symbol":"BPRAP","exchange":"Q","type":"stock","description":"Brookfield
|
| 90 |
+
Property REIT Inc 6 3\/8 % Cum Red Pfd Shs Series -A-"},{"symbol":"RA","exchange":"N","type":"stock","description":"Brookfield
|
| 91 |
+
Real Assets Income Fund Inc."},{"symbol":"BEP''A","exchange":"N","type":"stock","description":"Brookfield
|
| 92 |
+
Renewable Partners L.P. 5.25% Class A Preferred Limited Partnership Units,
|
| 93 |
+
Series 17"},{"symbol":"BEP\/WDA","exchange":"N","type":"stock","description":"Brookfield
|
| 94 |
+
Renewable Partners L.P. 5.25% Class A Preferred Limited Partnership Units,
|
| 95 |
+
Series 17"},{"symbol":"BKFRF","exchange":"V","type":"stock","description":"Brookfield
|
| 96 |
+
Renewable Partners LP 5.25% PRF PERPETUAL USD 25 Cls A"},{"symbol":"BROOF","exchange":"V","type":"stock","description":"Brookfield
|
| 97 |
+
Renewable Partners LP Cum Red Pfd Partnership Units -A- Series -11-"}]}}'
|
| 98 |
+
headers:
|
| 99 |
+
Access-Control-Allow-Headers:
|
| 100 |
+
- accept, authorization
|
| 101 |
+
Access-Control-Allow-Methods:
|
| 102 |
+
- GET, PUT, POST, DELETE
|
| 103 |
+
Access-Control-Allow-Origin:
|
| 104 |
+
- '*'
|
| 105 |
+
Connection:
|
| 106 |
+
- keep-alive
|
| 107 |
+
Content-Type:
|
| 108 |
+
- application/json;charset=UTF-8
|
| 109 |
+
Date:
|
| 110 |
+
- Sat, 09 Mar 2024 16:46:58 GMT
|
| 111 |
+
Transfer-Encoding:
|
| 112 |
+
- chunked
|
| 113 |
+
X-Ratelimit-Allowed:
|
| 114 |
+
- '200'
|
| 115 |
+
X-Ratelimit-Available:
|
| 116 |
+
- '199'
|
| 117 |
+
X-Ratelimit-Expiry:
|
| 118 |
+
- '1710002820000'
|
| 119 |
+
X-Ratelimit-Used:
|
| 120 |
+
- '1'
|
| 121 |
+
status:
|
| 122 |
+
code: 200
|
| 123 |
+
message: ''
|
| 124 |
+
version: 1
|
openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_search_fetcher_urllib3_v2.yaml
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interactions:
|
| 2 |
+
- request:
|
| 3 |
+
body: null
|
| 4 |
+
headers:
|
| 5 |
+
Accept:
|
| 6 |
+
- application/json
|
| 7 |
+
Authorization:
|
| 8 |
+
- MOCK_API_KEY
|
| 9 |
+
method: GET
|
| 10 |
+
uri: https://sandbox.tradier.com/v1/markets/search?q=brookfield&indexes=true
|
| 11 |
+
response:
|
| 12 |
+
body:
|
| 13 |
+
string: '{"securities":{"security":[{"symbol":"BPY","exchange":"Q","type":"stock","description":"Brookfield
|
| 14 |
+
Property Partners LP"},{"symbol":"BN","exchange":"N","type":"stock","description":"Brookfield
|
| 15 |
+
Corporation Class A Limited Voting Shares"},{"symbol":"BAM","exchange":"N","type":"stock","description":"Brookfield
|
| 16 |
+
Asset Management Inc Class A Limited Voting Shares"},{"symbol":"BPR","exchange":"Q","type":"stock","description":"Brookfield
|
| 17 |
+
Property REIT Inc"},{"symbol":"BEPC","exchange":"N","type":"stock","description":"Brookfield
|
| 18 |
+
Renewable Corporation Class A Subordinate Voting Shares"},{"symbol":"BPYU","exchange":"Q","type":"stock","description":"Brookfield
|
| 19 |
+
Property REIT Inc Cl A"},{"symbol":"BIPC","exchange":"N","type":"stock","description":"Brookfield
|
| 20 |
+
Infrastructure Corporation"},{"symbol":"BEP","exchange":"N","type":"stock","description":"Brookfield
|
| 21 |
+
Renewable Partners L.P."},{"symbol":"BIP","exchange":"N","type":"stock","description":"Brookfield
|
| 22 |
+
Infrastructure Partners LP Limited Partnership Units"},{"symbol":"BRP","exchange":"Q","type":"stock","description":"The
|
| 23 |
+
Baldwin Insurance Group Inc"},{"symbol":"BEPJ","exchange":"N","type":"stock","description":"Brookfield
|
| 24 |
+
BRP Holdings (Canada) Inc. 7.250% Perpetual Subordinated Notes"},{"symbol":"BPYPM","exchange":"Q","type":"stock","description":"Brookfield
|
| 25 |
+
Property Partners L.P. - 6.25% Class A Cumulative Redeemable Preferred Units,
|
| 26 |
+
Series 1"},{"symbol":"BPYUP","exchange":"Q","type":"stock","description":"Brookfield
|
| 27 |
+
Property REIT Inc 6 3\/8 % Cum Red Pfd Shs Series -A-"},{"symbol":"BBUC","exchange":"N","type":"stock","description":"Brookfield
|
| 28 |
+
Business Corporation Class A Exchangeable Subordinate Voting Shares"},{"symbol":"BIPJ","exchange":"N","type":"stock","description":"Brookfield
|
| 29 |
+
Infrastructure Corporation 7.250% Subordinated Notes due 2084"},{"symbol":"BNH","exchange":"N","type":"stock","description":"Brookfield
|
| 30 |
+
Finance Inc. 4.625% Subordinated Notes due October 16, 2080"},{"symbol":"BPYPN","exchange":"Q","type":"stock","description":"Brookfield
|
| 31 |
+
Property Partners L.P. - 5.750% Class A Cumulative Redeemable Perpetual Preferred
|
| 32 |
+
Units, Series 3"},{"symbol":"BEPH","exchange":"N","type":"stock","description":"Brookfield
|
| 33 |
+
BRP Holdings (Canada) Inc. 4.625% Perpetual Subordinated Notes"},{"symbol":"BAMH","exchange":"N","type":"stock","description":"Brookfield
|
| 34 |
+
Finance Inc. 4.625% Subordinated Notes due October 16, 2080"},{"symbol":"BAMI","exchange":"N","type":"stock","description":"Brookfield
|
| 35 |
+
Finance Inc. 4.50% Perpetual Subordinated Notes"},{"symbol":"BEPI","exchange":"N","type":"stock","description":"Brookfield
|
| 36 |
+
BRP Holdings (Canada) Inc. 4.875% Perpetual Subordinated Notes"},{"symbol":"BAMR","exchange":"N","type":"stock","description":"Brookfield
|
| 37 |
+
Asset Management Reinsurance Partners Ltd. Class A Exchangeable Limited Voting
|
| 38 |
+
Shares"},{"symbol":"BPYPO","exchange":"Q","type":"stock","description":"Brookfield
|
| 39 |
+
Property Partners L.P. - 6.375% Class A Cumulative Redeemable Perpetual Preferred
|
| 40 |
+
Units, Series 2"},{"symbol":"BPYPP","exchange":"Q","type":"stock","description":"Brookfield
|
| 41 |
+
Property Partners L.P. - 6.50% Class A Cumulative Redeemable Perpetual Preferred
|
| 42 |
+
Units"},{"symbol":"BNJ","exchange":"N","type":"stock","description":"Brookfield
|
| 43 |
+
Finance Inc. 4.50% Perpetual Subordinated Notes"},{"symbol":"BIPH","exchange":"N","type":"stock","description":"Brookfield
|
| 44 |
+
Infrastructure Corporation 5.000% Subordinated Notes due 2081"},{"symbol":"BBU","exchange":"N","type":"stock","description":"Brookfield
|
| 45 |
+
Business Partners L.P. Limited Partnership Units"},{"symbol":"BNRE","exchange":"N","type":"stock","description":"Brookfield
|
| 46 |
+
Reinsurance Ltd. Class A Exchangeable Limited Voting Shares"},{"symbol":"BNRE\/A","exchange":"N","type":"stock","description":"Brookfield
|
| 47 |
+
Reinsurance Ltd. Class A-1 Exchangeable Non-Voting Shares"},{"symbol":"BKFAF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 48 |
+
CORP A 2 by BROOKFIELD CORP."},{"symbol":"BAMGF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 49 |
+
CORP A 24 by BROOKFIELD CORP."},{"symbol":"BKAMF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 50 |
+
CORP PFD A 30 by BROOKFIELD CORP."},{"symbol":"BROXF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 51 |
+
CORP PFD A 37 by BROOKFIELD CORP."},{"symbol":"BAMKF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 52 |
+
CORP PREF 26 by BROOKFIELD CORP."},{"symbol":"BXDIF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 53 |
+
CORP PREF A 13 by BROOKFIELD CORP."},{"symbol":"BKEEF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 54 |
+
CP PF AA SR EE by Brookfield Office Properties Inc."},{"symbol":"BRPSF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 55 |
+
CP PREF A 17 by BROOKFIELD CORP."},{"symbol":"BKFOF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 56 |
+
CP PREF A 34 by BROOKFIELD CORP."},{"symbol":"BKFDF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 57 |
+
CP PREF A 40 by BROOKFIELD CORP."},{"symbol":"BRCFF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 58 |
+
CP PREF A 42 by BROOKFIELD CORP."},{"symbol":"BKFPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 59 |
+
CP PREF A 51 by BROOKFIELD CORP."},{"symbol":"BRFPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 60 |
+
CP SR 32 PRF A by BROOKFIELD CORP."},{"symbol":"DTLAP","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 61 |
+
DTLA 7.625A PR by Brookfield DTLA Inc."},{"symbol":"BRIPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 62 |
+
INF PNR PFD A by Brookfield Infrastructure Partners L.P."},{"symbol":"BKOFF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 63 |
+
OFCE PR II AAA by Brookfield Office Properties Inc."},{"symbol":"BOPPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 64 |
+
OFF PRF SER CC by Brookfield Office Properties Inc."},{"symbol":"BKAAF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 65 |
+
OFF PTY AAA N by Brookfield Office Properties Inc."},{"symbol":"BROAF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 66 |
+
OFF PTY AAA R by Brookfield Office Properties Inc."},{"symbol":"BRKNF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 67 |
+
OFF PTY PREF Y by Brookfield Office Properties Inc."},{"symbol":"BRKFF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 68 |
+
OFFCE PREF AAA by Brookfield Office Properties Inc."},{"symbol":"BRPYF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 69 |
+
OFFCE PROP W by Brookfield Office Properties Inc."},{"symbol":"BRPPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 70 |
+
OFFIE PPTY AAA by Brookfield Office Properties Inc."},{"symbol":"BROPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 71 |
+
PFD A SER 23 by Brookfield Office Properties Inc."},{"symbol":"BPSPF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 72 |
+
PTY SPLT PFD 2 by BROOKFIELD PPTY SPLIT CORP."},{"symbol":"BRENF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 73 |
+
RENWBE PFD UTS by Brookfield Renewable Partners L.P."},{"symbol":"BKFTF","exchange":"V","type":"stock","description":"BROOKFIELD
|
| 74 |
+
RENWBLE A SR 3 by BROOKFIELD RENEWABLE PWR PFD EQUITY INC"},{"symbol":"BAMW","exchange":"N","type":"stock","description":"Brookfield
|
| 75 |
+
Asset Management Inc Class A Limited Voting Shares When-Issued"},{"symbol":"BMKAF","exchange":"V","type":"stock","description":"Brookfield
|
| 76 |
+
Asset Management Inc Floating Rate Cum Pfd Registered Shs -A- Series -25-"},{"symbol":"BNW","exchange":"N","type":"stock","description":"Brookfield
|
| 77 |
+
Corporation Class A Limited Voting Shares When-Issued"},{"symbol":"DTLA\/WD","exchange":"N","type":"stock","description":"Brookfield
|
| 78 |
+
DTLA Inc. 7.625% Series A Cumulative Redeemable Preferred Stock"},{"symbol":"INF","exchange":"N","type":"stock","description":"Brookfield
|
| 79 |
+
Global Listed Infrastructure Income Fund Inc"},{"symbol":"BIPCW","exchange":"N","type":"stock","description":"Brookfield
|
| 80 |
+
Infrastructure Corp Ordinary Shares - Class A (Subordinate Share)"},{"symbol":"BIPFF","exchange":"V","type":"stock","description":"Brookfield
|
| 81 |
+
Infrastructure Partners LP 5% PRF PERPETUAL USD 25 - Ser 14 Cls A"},{"symbol":"BIP''B","exchange":"N","type":"stock","description":"Brookfield
|
| 82 |
+
Infrastructure Partners LP 5.000% Class A Preferred Limited Partnership Units,
|
| 83 |
+
Series 14"},{"symbol":"BIP\/WDB","exchange":"N","type":"stock","description":"Brookfield
|
| 84 |
+
Infrastructure Partners LP 5.000% Class A Preferred Limited Partnership Units,
|
| 85 |
+
Series 14"},{"symbol":"BIP''A","exchange":"N","type":"stock","description":"Brookfield
|
| 86 |
+
Infrastructure Partners LP 5.125% Class A Preferred Limited Partnership Units,
|
| 87 |
+
Series 13"},{"symbol":"BIP\/WDA","exchange":"N","type":"stock","description":"Brookfield
|
| 88 |
+
Infrastructure Partners LP 5.125% Class A Preferred Limited Partnership Units,
|
| 89 |
+
Series 13"},{"symbol":"BIPPF","exchange":"V","type":"stock","description":"Brookfield
|
| 90 |
+
Infrastructure Partners LP 5.125% PRF PERPETUAL USD 25 - Ser 13 Cls A"},{"symbol":"BIPAF","exchange":"V","type":"stock","description":"Brookfield
|
| 91 |
+
Infrastructure Partners LP Cum Red Pfd Partnership Units -A- Series -5-"},{"symbol":"BINFF","exchange":"V","type":"stock","description":"Brookfield
|
| 92 |
+
Infrastructure Partners LP Cum Red Pfd Partnership Units -A- Series -7-"},{"symbol":"OAK''B","exchange":"N","type":"stock","description":"Brookfield
|
| 93 |
+
Oaktree Holdings, LLC 6.550% Series B Preferred Units"},{"symbol":"OAK''A","exchange":"N","type":"stock","description":"Brookfield
|
| 94 |
+
Oaktree Holdings, LLC 6.625% Series A Preferred Units"},{"symbol":"BOPTP","exchange":"V","type":"stock","description":"Brookfield
|
| 95 |
+
Property Partners LP 5.75% PRF PERPETUAL USD 25 - Cls A Ser 3"},{"symbol":"BKOYF","exchange":"V","type":"stock","description":"Brookfield
|
| 96 |
+
Property Partners LP 6.375% PRF PERPETUAL USD 25 - Cls A Ser 2"},{"symbol":"BRKPF","exchange":"V","type":"stock","description":"Brookfield
|
| 97 |
+
Property Partners LP Cls A Ser 1"},{"symbol":"BPRAP","exchange":"Q","type":"stock","description":"Brookfield
|
| 98 |
+
Property REIT Inc 6 3\/8 % Cum Red Pfd Shs Series -A-"},{"symbol":"RA","exchange":"N","type":"stock","description":"Brookfield
|
| 99 |
+
Real Assets Income Fund Inc."},{"symbol":"BEP''A","exchange":"N","type":"stock","description":"Brookfield
|
| 100 |
+
Renewable Partners L.P. 5.25% Class A Preferred Limited Partnership Units,
|
| 101 |
+
Series 17"},{"symbol":"BEP\/WDA","exchange":"N","type":"stock","description":"Brookfield
|
| 102 |
+
Renewable Partners L.P. 5.25% Class A Preferred Limited Partnership Units,
|
| 103 |
+
Series 17"},{"symbol":"BKFRF","exchange":"V","type":"stock","description":"Brookfield
|
| 104 |
+
Renewable Partners LP 5.25% PRF PERPETUAL USD 25 Cls A"},{"symbol":"BROOF","exchange":"V","type":"stock","description":"Brookfield
|
| 105 |
+
Renewable Partners LP Cum Red Pfd Partnership Units -A- Series -11-"}]}}'
|
| 106 |
+
headers:
|
| 107 |
+
Access-Control-Allow-Headers:
|
| 108 |
+
- accept, authorization
|
| 109 |
+
Access-Control-Allow-Methods:
|
| 110 |
+
- GET, PUT, POST, DELETE
|
| 111 |
+
Access-Control-Allow-Origin:
|
| 112 |
+
- '*'
|
| 113 |
+
Connection:
|
| 114 |
+
- keep-alive
|
| 115 |
+
Content-Type:
|
| 116 |
+
- application/json;charset=UTF-8
|
| 117 |
+
Date:
|
| 118 |
+
- Fri, 28 Jun 2024 16:06:01 GMT
|
| 119 |
+
Transfer-Encoding:
|
| 120 |
+
- chunked
|
| 121 |
+
X-Ratelimit-Allowed:
|
| 122 |
+
- '200'
|
| 123 |
+
X-Ratelimit-Available:
|
| 124 |
+
- '199'
|
| 125 |
+
X-Ratelimit-Expiry:
|
| 126 |
+
- '1719590820000'
|
| 127 |
+
X-Ratelimit-Used:
|
| 128 |
+
- '1'
|
| 129 |
+
status:
|
| 130 |
+
code: 200
|
| 131 |
+
message: ''
|
| 132 |
+
version: 1
|
openbb_platform/providers/tradier/tests/test_tradier_fetchers.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tradier Fetchers Tests."""
|
| 2 |
+
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
from openbb_core.app.service.user_service import UserService
|
| 7 |
+
from openbb_tradier.models.equity_historical import TradierEquityHistoricalFetcher
|
| 8 |
+
from openbb_tradier.models.equity_quote import TradierEquityQuoteFetcher
|
| 9 |
+
from openbb_tradier.models.equity_search import TradierEquitySearchFetcher
|
| 10 |
+
from openbb_tradier.models.options_chains import TradierOptionsChainsFetcher
|
| 11 |
+
|
| 12 |
+
test_credentials = UserService().default_user_settings.credentials.model_dump(
|
| 13 |
+
mode="json"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@pytest.fixture(scope="module")
|
| 18 |
+
def vcr_config():
|
| 19 |
+
"""VCR configuration."""
|
| 20 |
+
return {
|
| 21 |
+
"filter_headers": [
|
| 22 |
+
("User-Agent", None),
|
| 23 |
+
("Authorization", "MOCK_API_KEY"),
|
| 24 |
+
],
|
| 25 |
+
"filter_query_parameters": [],
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@pytest.mark.record_http
|
| 30 |
+
def test_tradier_equity_historical_fetcher(credentials=test_credentials):
|
| 31 |
+
"""Test the Tradier Equity Historical fetcher."""
|
| 32 |
+
params = {
|
| 33 |
+
"start_date": datetime(2024, 2, 1).date(),
|
| 34 |
+
"end_date": datetime(2024, 2, 29).date(),
|
| 35 |
+
"symbol": "AAPL",
|
| 36 |
+
"interval": "1d",
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
fetcher = TradierEquityHistoricalFetcher()
|
| 40 |
+
result = fetcher.test(params, credentials)
|
| 41 |
+
assert result is None
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@pytest.mark.record_http
|
| 45 |
+
def test_tradier_equity_search_fetcher(credentials=test_credentials):
|
| 46 |
+
"""Test the Tradier Equity Search fetcher."""
|
| 47 |
+
params = {
|
| 48 |
+
"query": "brookfield",
|
| 49 |
+
"is_symbol": False,
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
fetcher = TradierEquitySearchFetcher()
|
| 53 |
+
result = fetcher.test(params, credentials)
|
| 54 |
+
assert result is None
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
@pytest.mark.record_http
|
| 58 |
+
def test_tradier_equity_quote_fetcher(credentials=test_credentials):
|
| 59 |
+
"""Test the Tradier Equity Quote fetcher."""
|
| 60 |
+
params = {"symbol": "SPY,SPY251219P00450000"}
|
| 61 |
+
|
| 62 |
+
fetcher = TradierEquityQuoteFetcher()
|
| 63 |
+
result = fetcher.test(params, credentials)
|
| 64 |
+
assert result is None
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@pytest.mark.record_http
|
| 68 |
+
def test_tradier_derivatives_options_chains_fetcher(credentials=test_credentials):
|
| 69 |
+
"""Test the Tradier Derivatives Options Chains fetcher."""
|
| 70 |
+
|
| 71 |
+
params = {"symbol": "PLTR"}
|
| 72 |
+
|
| 73 |
+
fetcher = TradierOptionsChainsFetcher()
|
| 74 |
+
result = fetcher.test(params, credentials)
|
| 75 |
+
assert result is None
|
openbb_platform/providers/tradingeconomics/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenBB Trading Economics Provider
|
| 2 |
+
|
| 3 |
+
This extension integrates the [Trading Economics](https://docs.tradingeconomics.com/) data provider into the OpenBB SDK.
|
| 4 |
+
|
| 5 |
+
## Installation
|
| 6 |
+
|
| 7 |
+
To install the extension:
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
pip install openbb-tradingeconomics
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
Documentation available [here](https://docs.openbb.co/platform/developer_guide/contributing).
|
openbb_platform/providers/tradingeconomics/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""OpenBB Trading Economics Provider."""
|
openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Trading Economics provider module."""
|
| 2 |
+
|
| 3 |
+
from openbb_core.provider.abstract.provider import Provider
|
| 4 |
+
from openbb_tradingeconomics.models.economic_calendar import TEEconomicCalendarFetcher
|
| 5 |
+
|
| 6 |
+
tradingeconomics_provider = Provider(
|
| 7 |
+
name="tradingeconomics",
|
| 8 |
+
website="https://tradingeconomics.com",
|
| 9 |
+
description="""Trading Economics provides its users with accurate information for
|
| 10 |
+
196 countries including historical data and forecasts for more than 20 million economic
|
| 11 |
+
indicators, exchange rates, stock market indexes, government bond yields and commodity
|
| 12 |
+
prices. Our data for economic indicators is based on official sources, not third party
|
| 13 |
+
data providers, and our facts are regularly checked for inconsistencies.
|
| 14 |
+
Trading Economics has received nearly 2 billion page views from all around the
|
| 15 |
+
world.""",
|
| 16 |
+
credentials=["api_key"],
|
| 17 |
+
fetcher_dict={"EconomicCalendar": TEEconomicCalendarFetcher},
|
| 18 |
+
repr_name="Trading Economics",
|
| 19 |
+
)
|
openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/models/economic_calendar.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Trading Economics Economic Calendar Model."""
|
| 2 |
+
|
| 3 |
+
# pylint: disable=unused-argument
|
| 4 |
+
|
| 5 |
+
from datetime import (
|
| 6 |
+
date as dateType,
|
| 7 |
+
datetime,
|
| 8 |
+
)
|
| 9 |
+
from typing import Any, Literal, Optional, Union
|
| 10 |
+
from warnings import warn
|
| 11 |
+
|
| 12 |
+
from openbb_core.app.model.abstract.error import OpenBBError
|
| 13 |
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
| 14 |
+
from openbb_core.provider.standard_models.economic_calendar import (
|
| 15 |
+
EconomicCalendarData,
|
| 16 |
+
EconomicCalendarQueryParams,
|
| 17 |
+
)
|
| 18 |
+
from openbb_tradingeconomics.utils.countries import COUNTRIES
|
| 19 |
+
from pydantic import Field, field_validator, model_validator
|
| 20 |
+
|
| 21 |
+
IMPORTANCE_CHOICES = ["low", "medium", "high"]
|
| 22 |
+
|
| 23 |
+
IMPORTANCE = Literal["low", "medium", "high"]
|
| 24 |
+
|
| 25 |
+
GROUPS_CHOICES = [
|
| 26 |
+
"interest_rate",
|
| 27 |
+
"inflation",
|
| 28 |
+
"bonds",
|
| 29 |
+
"consumer",
|
| 30 |
+
"gdp",
|
| 31 |
+
"government",
|
| 32 |
+
"housing",
|
| 33 |
+
"labour",
|
| 34 |
+
"markets",
|
| 35 |
+
"money",
|
| 36 |
+
"prices",
|
| 37 |
+
"trade",
|
| 38 |
+
"business",
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
GROUPS = Literal[
|
| 42 |
+
"interest_rate",
|
| 43 |
+
"inflation",
|
| 44 |
+
"bonds",
|
| 45 |
+
"consumer",
|
| 46 |
+
"gdp",
|
| 47 |
+
"government",
|
| 48 |
+
"housing",
|
| 49 |
+
"labour",
|
| 50 |
+
"markets",
|
| 51 |
+
"money",
|
| 52 |
+
"prices",
|
| 53 |
+
"trade",
|
| 54 |
+
"business",
|
| 55 |
+
]
|
| 56 |
+
|
| 57 |
+
TE_COUNTRY_LIMIT = 28
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class TEEconomicCalendarQueryParams(EconomicCalendarQueryParams):
|
| 61 |
+
"""Trading Economics Economic Calendar Query.
|
| 62 |
+
|
| 63 |
+
Source: https://docs.tradingeconomics.com/economic_calendar/
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
__json_schema_extra__ = {
|
| 67 |
+
"country": {
|
| 68 |
+
"multiple_items_allowed": True,
|
| 69 |
+
"choices": sorted(COUNTRIES),
|
| 70 |
+
},
|
| 71 |
+
"calendar_id": {
|
| 72 |
+
"multiple_items_allowed": True,
|
| 73 |
+
},
|
| 74 |
+
"importance": {
|
| 75 |
+
"multiple_items_allowed": False,
|
| 76 |
+
"choices": IMPORTANCE_CHOICES,
|
| 77 |
+
},
|
| 78 |
+
"group": {
|
| 79 |
+
"multiple_items_allowed": False,
|
| 80 |
+
"choices": GROUPS_CHOICES,
|
| 81 |
+
},
|
| 82 |
+
}
|
| 83 |
+
country: Optional[str] = Field(
|
| 84 |
+
default=None,
|
| 85 |
+
description="Country of the event.",
|
| 86 |
+
)
|
| 87 |
+
importance: Optional[IMPORTANCE] = Field(
|
| 88 |
+
default=None,
|
| 89 |
+
description="Importance of the event.",
|
| 90 |
+
)
|
| 91 |
+
group: Optional[GROUPS] = Field(
|
| 92 |
+
default=None,
|
| 93 |
+
description="Grouping of events.",
|
| 94 |
+
)
|
| 95 |
+
calendar_id: Union[None, int, str] = Field(
|
| 96 |
+
default=None, description="Get events by TradingEconomics Calendar ID."
|
| 97 |
+
)
|
| 98 |
+
_number_of_countries: int = 0
|
| 99 |
+
|
| 100 |
+
@field_validator("country", mode="before", check_fields=False)
|
| 101 |
+
@classmethod
|
| 102 |
+
def validate_country(cls, c):
|
| 103 |
+
"""Validate country."""
|
| 104 |
+
# pylint: disable=import-outside-toplevel
|
| 105 |
+
from openbb_core.provider.utils.helpers import check_item
|
| 106 |
+
|
| 107 |
+
result: list = []
|
| 108 |
+
values = c.replace(" ", "_").split(",")
|
| 109 |
+
for v in values:
|
| 110 |
+
check_item(v.lower(), COUNTRIES)
|
| 111 |
+
result.append(v.lower())
|
| 112 |
+
|
| 113 |
+
cls._number_of_countries = len(result)
|
| 114 |
+
if cls._number_of_countries >= TE_COUNTRY_LIMIT:
|
| 115 |
+
warn(
|
| 116 |
+
f"Trading Economics API tend to fail if the number of countries is above {TE_COUNTRY_LIMIT}."
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
return ",".join(result)
|
| 120 |
+
|
| 121 |
+
@field_validator("importance", mode="after", check_fields=False)
|
| 122 |
+
@classmethod
|
| 123 |
+
def importance_to_number(cls, v):
|
| 124 |
+
"""Convert importance to number."""
|
| 125 |
+
string_to_value = {"low": 1, "medium": 2, "high": 3}
|
| 126 |
+
return string_to_value.get(v.lower(), None) if v else None
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class TEEconomicCalendarData(EconomicCalendarData):
|
| 130 |
+
"""Trading Economics Economic Calendar Data."""
|
| 131 |
+
|
| 132 |
+
__alias_dict__ = {
|
| 133 |
+
"date": "Date",
|
| 134 |
+
"country": "Country",
|
| 135 |
+
"category": "Category",
|
| 136 |
+
"event": "Event",
|
| 137 |
+
"reference": "Reference",
|
| 138 |
+
"reference_date": "ReferenceDate",
|
| 139 |
+
"source": "Source",
|
| 140 |
+
"source_url": "SourceURL",
|
| 141 |
+
"actual": "Actual",
|
| 142 |
+
"consensus": "Forecast",
|
| 143 |
+
"forecast": "TEForecast",
|
| 144 |
+
"te_url": "URL",
|
| 145 |
+
"importance": "Importance",
|
| 146 |
+
"currency": "Currency",
|
| 147 |
+
"unit": "Unit",
|
| 148 |
+
"ticker": "Ticker",
|
| 149 |
+
"symbol": "Symbol",
|
| 150 |
+
"previous": "Previous",
|
| 151 |
+
"revised": "Revised",
|
| 152 |
+
"last_updated": "LastUpdate",
|
| 153 |
+
"calendar_id": "CalendarId",
|
| 154 |
+
"date_span": "DateSpan",
|
| 155 |
+
}
|
| 156 |
+
forecast: Optional[Union[str, float]] = Field(
|
| 157 |
+
default=None, description="TradingEconomics projections."
|
| 158 |
+
)
|
| 159 |
+
reference: Optional[str] = Field(
|
| 160 |
+
default=None,
|
| 161 |
+
description="Abbreviated period for which released data refers to.",
|
| 162 |
+
)
|
| 163 |
+
reference_date: Optional[dateType] = Field(
|
| 164 |
+
default=None, description="Date for the reference period."
|
| 165 |
+
)
|
| 166 |
+
calendar_id: Optional[int] = Field(
|
| 167 |
+
default=None, description="TradingEconomics Calendar ID."
|
| 168 |
+
)
|
| 169 |
+
date_span: Optional[int] = Field(
|
| 170 |
+
default=None, description="Date span of the event."
|
| 171 |
+
)
|
| 172 |
+
symbol: Optional[str] = Field(default=None, description="TradingEconomics Symbol.")
|
| 173 |
+
ticker: Optional[str] = Field(
|
| 174 |
+
default=None, description="TradingEconomics Ticker symbol."
|
| 175 |
+
)
|
| 176 |
+
te_url: Optional[str] = Field(
|
| 177 |
+
default=None, description="TradingEconomics URL path."
|
| 178 |
+
)
|
| 179 |
+
source_url: Optional[str] = Field(default=None, description="Source URL.")
|
| 180 |
+
last_updated: Optional[datetime] = Field(
|
| 181 |
+
default=None, description="Last update of the data."
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
@field_validator("importance", mode="before", check_fields=False)
|
| 185 |
+
@classmethod
|
| 186 |
+
def importance_to_number(cls, v):
|
| 187 |
+
"""Convert importance to number."""
|
| 188 |
+
value_to_string = {1: "Low", 2: "Medium", 3: "High"}
|
| 189 |
+
return value_to_string.get(v) if v else None
|
| 190 |
+
|
| 191 |
+
@field_validator("date", "last_updated", mode="before", check_fields=False)
|
| 192 |
+
@classmethod
|
| 193 |
+
def validate_datetime(cls, v: str) -> datetime:
|
| 194 |
+
"""Validate the datetime values."""
|
| 195 |
+
# pylint: disable=import-outside-toplevel
|
| 196 |
+
from pandas import to_datetime
|
| 197 |
+
|
| 198 |
+
dt = to_datetime(v, utc=True)
|
| 199 |
+
return dt.replace(microsecond=0)
|
| 200 |
+
|
| 201 |
+
@field_validator("reference_date", mode="before", check_fields=False)
|
| 202 |
+
@classmethod
|
| 203 |
+
def validate_date(cls, v):
|
| 204 |
+
"""Validate the date."""
|
| 205 |
+
# pylint: disable=import-outside-toplevel
|
| 206 |
+
from pandas import to_datetime
|
| 207 |
+
|
| 208 |
+
return to_datetime(v, utc=True).date() if v else None
|
| 209 |
+
|
| 210 |
+
@model_validator(mode="before")
|
| 211 |
+
@classmethod
|
| 212 |
+
def empty_strings(cls, values): # pylint: disable=no-self-argument
|
| 213 |
+
"""Replace empty strings with None."""
|
| 214 |
+
return (
|
| 215 |
+
{
|
| 216 |
+
k: None if isinstance(v, str) and v == "" else v
|
| 217 |
+
for k, v in values.items()
|
| 218 |
+
}
|
| 219 |
+
if isinstance(values, dict)
|
| 220 |
+
else values
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class TEEconomicCalendarFetcher(
|
| 225 |
+
Fetcher[
|
| 226 |
+
TEEconomicCalendarQueryParams,
|
| 227 |
+
list[TEEconomicCalendarData],
|
| 228 |
+
]
|
| 229 |
+
):
|
| 230 |
+
"""Transform the query, extract and transform the data from the Trading Economics endpoints."""
|
| 231 |
+
|
| 232 |
+
@staticmethod
|
| 233 |
+
def transform_query(params: dict[str, Any]) -> TEEconomicCalendarQueryParams:
|
| 234 |
+
"""Transform the query params."""
|
| 235 |
+
return TEEconomicCalendarQueryParams(**params)
|
| 236 |
+
|
| 237 |
+
@staticmethod
|
| 238 |
+
async def aextract_data(
|
| 239 |
+
query: TEEconomicCalendarQueryParams,
|
| 240 |
+
credentials: Optional[dict[str, str]],
|
| 241 |
+
**kwargs: Any,
|
| 242 |
+
) -> Union[dict, list[dict]]:
|
| 243 |
+
"""Return the raw data from the TE endpoint."""
|
| 244 |
+
# pylint: disable=import-outside-toplevel
|
| 245 |
+
from openbb_core.provider.utils.helpers import amake_request
|
| 246 |
+
from openbb_tradingeconomics.utils import url_generator
|
| 247 |
+
from openbb_tradingeconomics.utils.helpers import response_callback
|
| 248 |
+
|
| 249 |
+
api_key = credentials.get("tradingeconomics_api_key") if credentials else ""
|
| 250 |
+
|
| 251 |
+
if query.group is not None:
|
| 252 |
+
query.group = query.group.replace("_", " ") # type: ignore
|
| 253 |
+
|
| 254 |
+
url = url_generator.generate_url(query)
|
| 255 |
+
|
| 256 |
+
if not url:
|
| 257 |
+
raise OpenBBError(
|
| 258 |
+
"No url generated. Check combination of input parameters."
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
url = f"{url}{api_key}"
|
| 262 |
+
|
| 263 |
+
return await amake_request(url, response_callback=response_callback, **kwargs)
|
| 264 |
+
|
| 265 |
+
@staticmethod
|
| 266 |
+
def transform_data(
|
| 267 |
+
query: TEEconomicCalendarQueryParams, data: list[dict], **kwargs: Any
|
| 268 |
+
) -> list[TEEconomicCalendarData]:
|
| 269 |
+
"""Return the transformed data."""
|
| 270 |
+
return [TEEconomicCalendarData.model_validate(d) for d in data]
|
openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/countries.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Countries list for Trading Economics API."""
|
| 2 |
+
|
| 3 |
+
country_dict = {
|
| 4 |
+
"G20": [
|
| 5 |
+
"United States",
|
| 6 |
+
"Euro Area",
|
| 7 |
+
"China",
|
| 8 |
+
"Japan",
|
| 9 |
+
"Germany",
|
| 10 |
+
"United Kingdom",
|
| 11 |
+
"France",
|
| 12 |
+
"India",
|
| 13 |
+
"Italy",
|
| 14 |
+
"Brazil",
|
| 15 |
+
"Canada",
|
| 16 |
+
"South Korea",
|
| 17 |
+
"Russia",
|
| 18 |
+
"Spain",
|
| 19 |
+
],
|
| 20 |
+
"Australia": [
|
| 21 |
+
"Australia",
|
| 22 |
+
"Fiji",
|
| 23 |
+
"Kiribati",
|
| 24 |
+
"New Caledonia",
|
| 25 |
+
"New Zealand",
|
| 26 |
+
"Papua New Guinea",
|
| 27 |
+
"Samoa",
|
| 28 |
+
"Solomon Islands",
|
| 29 |
+
"Tonga",
|
| 30 |
+
"Vanuatu",
|
| 31 |
+
],
|
| 32 |
+
"America": [
|
| 33 |
+
"Antigua and Barbuda",
|
| 34 |
+
"Argentina",
|
| 35 |
+
"Aruba",
|
| 36 |
+
"Bahamas",
|
| 37 |
+
"Barbados",
|
| 38 |
+
"Belize",
|
| 39 |
+
"Bermuda",
|
| 40 |
+
"Bolivia",
|
| 41 |
+
"Brazil",
|
| 42 |
+
"Canada",
|
| 43 |
+
"Cayman Islands",
|
| 44 |
+
"Chile",
|
| 45 |
+
"Colombia",
|
| 46 |
+
"Costa Rica",
|
| 47 |
+
"Cuba",
|
| 48 |
+
"Dominica",
|
| 49 |
+
"Dominican Republic",
|
| 50 |
+
"Ecuador",
|
| 51 |
+
"El Salvador",
|
| 52 |
+
"Grenada",
|
| 53 |
+
"Guatemala",
|
| 54 |
+
"Guyana",
|
| 55 |
+
"Haiti",
|
| 56 |
+
"Honduras",
|
| 57 |
+
"Jamaica",
|
| 58 |
+
"Mexico",
|
| 59 |
+
"Nicaragua",
|
| 60 |
+
"Panama",
|
| 61 |
+
"Paraguay",
|
| 62 |
+
"Peru",
|
| 63 |
+
"Puerto Rico",
|
| 64 |
+
"Suriname",
|
| 65 |
+
"Trinidad and Tobago",
|
| 66 |
+
"United States",
|
| 67 |
+
"Uruguay",
|
| 68 |
+
"Venezuela",
|
| 69 |
+
],
|
| 70 |
+
"Europe": [
|
| 71 |
+
"Albania",
|
| 72 |
+
"Andorra",
|
| 73 |
+
"Austria",
|
| 74 |
+
"Belarus",
|
| 75 |
+
"Belgium",
|
| 76 |
+
"Bosnia and Herzegovina",
|
| 77 |
+
"Bulgaria",
|
| 78 |
+
"Croatia",
|
| 79 |
+
"Cyprus",
|
| 80 |
+
"Czech Republic",
|
| 81 |
+
"Denmark",
|
| 82 |
+
"Estonia",
|
| 83 |
+
"Euro area",
|
| 84 |
+
"Faroe Islands",
|
| 85 |
+
"Finland",
|
| 86 |
+
"France",
|
| 87 |
+
"Germany",
|
| 88 |
+
"Greece",
|
| 89 |
+
"Hungary",
|
| 90 |
+
"Iceland",
|
| 91 |
+
"Ireland",
|
| 92 |
+
"Isle of Man",
|
| 93 |
+
"Italy",
|
| 94 |
+
"Kosovo",
|
| 95 |
+
"Latvia",
|
| 96 |
+
"Liechtenstein",
|
| 97 |
+
"Lithuania",
|
| 98 |
+
"Luxembourg",
|
| 99 |
+
"Malta",
|
| 100 |
+
"Moldova",
|
| 101 |
+
"Monaco",
|
| 102 |
+
"Montenegro",
|
| 103 |
+
"Netherlands",
|
| 104 |
+
"North Macedonia",
|
| 105 |
+
"Norway",
|
| 106 |
+
"Poland",
|
| 107 |
+
"Portugal",
|
| 108 |
+
"Romania",
|
| 109 |
+
"Russia",
|
| 110 |
+
"Serbia",
|
| 111 |
+
"Slovakia",
|
| 112 |
+
"Slovenia",
|
| 113 |
+
"Spain",
|
| 114 |
+
"Sweden",
|
| 115 |
+
"Switzerland",
|
| 116 |
+
"Turkey",
|
| 117 |
+
"Ukraine",
|
| 118 |
+
"United Kingdom",
|
| 119 |
+
],
|
| 120 |
+
"Africa": [
|
| 121 |
+
"Algeria",
|
| 122 |
+
"Angola",
|
| 123 |
+
"Benin",
|
| 124 |
+
"Botswana",
|
| 125 |
+
"Burkina Faso",
|
| 126 |
+
"Burundi",
|
| 127 |
+
"Cameroon",
|
| 128 |
+
"Cape Verde",
|
| 129 |
+
"Central African Republic",
|
| 130 |
+
"Chad",
|
| 131 |
+
"Comoros",
|
| 132 |
+
"Congo",
|
| 133 |
+
"Djibouti",
|
| 134 |
+
"Egypt",
|
| 135 |
+
"Equatorial Guinea",
|
| 136 |
+
"Eritrea",
|
| 137 |
+
"Ethiopia",
|
| 138 |
+
"Gabon",
|
| 139 |
+
"Gambia",
|
| 140 |
+
"Ghana",
|
| 141 |
+
"Guinea",
|
| 142 |
+
"Guinea Bissau",
|
| 143 |
+
"Ivory Coast",
|
| 144 |
+
"Kenya",
|
| 145 |
+
"Lesotho",
|
| 146 |
+
"Liberia",
|
| 147 |
+
"Libya",
|
| 148 |
+
"Madagascar",
|
| 149 |
+
"Malawi",
|
| 150 |
+
"Mali",
|
| 151 |
+
"Mauritania",
|
| 152 |
+
"Mauritius",
|
| 153 |
+
"Morocco",
|
| 154 |
+
"Mozambique",
|
| 155 |
+
"Namibia",
|
| 156 |
+
"Niger",
|
| 157 |
+
"Nigeria",
|
| 158 |
+
"Republic of the Congo",
|
| 159 |
+
"Rwanda",
|
| 160 |
+
"Sao Tome and Principe",
|
| 161 |
+
"Senegal",
|
| 162 |
+
"Seychelles",
|
| 163 |
+
"Sierra Leone",
|
| 164 |
+
"Somalia",
|
| 165 |
+
"South Africa",
|
| 166 |
+
"South Sudan",
|
| 167 |
+
"Sudan",
|
| 168 |
+
"Swaziland",
|
| 169 |
+
"Tanzania",
|
| 170 |
+
"Togo",
|
| 171 |
+
"Tunisia",
|
| 172 |
+
"Uganda",
|
| 173 |
+
"Zambia",
|
| 174 |
+
"Zimbabwe",
|
| 175 |
+
],
|
| 176 |
+
"Asia": [
|
| 177 |
+
"Afghanistan",
|
| 178 |
+
"Armenia",
|
| 179 |
+
"Azerbaijan",
|
| 180 |
+
"Bahrain",
|
| 181 |
+
"Bangladesh",
|
| 182 |
+
"Bhutan",
|
| 183 |
+
"Brunei",
|
| 184 |
+
"Cambodia",
|
| 185 |
+
"China",
|
| 186 |
+
"East Timor",
|
| 187 |
+
"Georgia",
|
| 188 |
+
"Hong Kong",
|
| 189 |
+
"India",
|
| 190 |
+
"Indonesia",
|
| 191 |
+
"Iran",
|
| 192 |
+
"Iraq",
|
| 193 |
+
"Israel",
|
| 194 |
+
"Japan",
|
| 195 |
+
"Jordan",
|
| 196 |
+
"Kazakhstan",
|
| 197 |
+
"Kuwait",
|
| 198 |
+
"Kyrgyzstan",
|
| 199 |
+
"Laos",
|
| 200 |
+
"Lebanon",
|
| 201 |
+
"Macao",
|
| 202 |
+
"Malaysia",
|
| 203 |
+
"Maldives",
|
| 204 |
+
"Mongolia",
|
| 205 |
+
"Myanmar",
|
| 206 |
+
"Nepal",
|
| 207 |
+
"North Korea",
|
| 208 |
+
"Oman",
|
| 209 |
+
"Palestine",
|
| 210 |
+
"Pakistan",
|
| 211 |
+
"Philippines",
|
| 212 |
+
"Qatar",
|
| 213 |
+
"Saudi Arabia",
|
| 214 |
+
"Singapore",
|
| 215 |
+
"South Korea",
|
| 216 |
+
"Sri Lanka",
|
| 217 |
+
"Syria",
|
| 218 |
+
"Taiwan",
|
| 219 |
+
"Tajikistan",
|
| 220 |
+
"Thailand",
|
| 221 |
+
"Turkmenistan",
|
| 222 |
+
"United Arab Emirates",
|
| 223 |
+
"Uzbekistan",
|
| 224 |
+
"Vietnam",
|
| 225 |
+
"Yemen",
|
| 226 |
+
],
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
COUNTRIES = list(
|
| 230 |
+
{
|
| 231 |
+
item.lower().replace(" ", "_")
|
| 232 |
+
for sublist in country_dict.values()
|
| 233 |
+
for item in sublist
|
| 234 |
+
}
|
| 235 |
+
)
|
openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/helpers.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""TradingEconomics Helpers."""
|
| 2 |
+
|
| 3 |
+
from typing import Union
|
| 4 |
+
|
| 5 |
+
from openbb_core.app.model.abstract.error import OpenBBError
|
| 6 |
+
from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
async def response_callback(response, _) -> Union[dict, list[dict]]:
|
| 10 |
+
"""Return the response."""
|
| 11 |
+
if response.status != 200:
|
| 12 |
+
message = await response.text()
|
| 13 |
+
|
| 14 |
+
if "credentials" in message or "unauthorized" in message.lower():
|
| 15 |
+
raise UnauthorizedError(
|
| 16 |
+
f"Unauthorized TradingEconomics request -> {message}"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
raise OpenBBError(f"{response.status} -> {message}")
|
| 20 |
+
|
| 21 |
+
results = await response.json()
|
| 22 |
+
|
| 23 |
+
if not results:
|
| 24 |
+
raise EmptyDataError("The request was returned empty.")
|
| 25 |
+
|
| 26 |
+
return results
|
openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/url_generator.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Helper to generate urls for Trading Economics API."""
|
| 2 |
+
|
| 3 |
+
from datetime import date
|
| 4 |
+
from typing import Dict, List
|
| 5 |
+
from urllib.parse import quote, urlencode
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def check_args(query_args: Dict, to_include: List[str]):
|
| 9 |
+
"""Check if all fields in to_include are present in query_args."""
|
| 10 |
+
available_args = ["country", "start_date", "end_date", "importance", "group"]
|
| 11 |
+
|
| 12 |
+
# Check if all fields in to_include are present in query_args
|
| 13 |
+
# and elements in available_args that are not in to_include are not present in query_args
|
| 14 |
+
return all(field in query_args for field in to_include) and all(
|
| 15 |
+
field not in query_args for field in available_args if field not in to_include
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# pylint: disable = R0912
|
| 20 |
+
def generate_url(in_query):
|
| 21 |
+
"""Generate the url for trading economimcs.
|
| 22 |
+
|
| 23 |
+
There is not a single api endpoint to hit so these are generated based on the combinations.
|
| 24 |
+
There are also some combinations that return no data so that will return an empty string.
|
| 25 |
+
"""
|
| 26 |
+
# Converting the input query to a dict of params that are not None
|
| 27 |
+
query = {k: v for k, v in in_query.dict().items() if v is not None}
|
| 28 |
+
|
| 29 |
+
# Nothing -- just a snapshot
|
| 30 |
+
if not query:
|
| 31 |
+
return "https://api.tradingeconomics.com/calendar?c="
|
| 32 |
+
|
| 33 |
+
# Both start and end date are required
|
| 34 |
+
if "start_date" in query and "end_date" not in query:
|
| 35 |
+
query["end_date"] = date.today().strftime("%Y-%m-%d")
|
| 36 |
+
if "end_date" in query and "start_date" not in query:
|
| 37 |
+
query["start_date"] = query["end_date"]
|
| 38 |
+
|
| 39 |
+
# Handle the formatting for the api
|
| 40 |
+
if "country" in query:
|
| 41 |
+
country = quote(query["country"].replace("_", " "))
|
| 42 |
+
if "group" in query:
|
| 43 |
+
group = quote(query["group"])
|
| 44 |
+
|
| 45 |
+
base_url = "https://api.tradingeconomics.com/calendar"
|
| 46 |
+
url = ""
|
| 47 |
+
|
| 48 |
+
# Construct URL based on query parameters
|
| 49 |
+
# Country Only
|
| 50 |
+
if check_args(query, ["country"]):
|
| 51 |
+
# pylint: disable=possibly-used-before-assignment
|
| 52 |
+
url = f"{base_url}/country/{country}?c="
|
| 53 |
+
# Country + Date
|
| 54 |
+
elif check_args(query, ["country", "start_date", "end_date"]):
|
| 55 |
+
url = (
|
| 56 |
+
f'{base_url}/country/{country}/{query["start_date"]}/{query["end_date"]}?c='
|
| 57 |
+
)
|
| 58 |
+
# Country + Importance
|
| 59 |
+
elif check_args(query, ["country", "importance"]):
|
| 60 |
+
url = f"{base_url}/country/{country}?{urlencode(query)}&c="
|
| 61 |
+
# Country + Group
|
| 62 |
+
elif check_args(query, ["country", "group"]):
|
| 63 |
+
# pylint: disable=possibly-used-before-assignment
|
| 64 |
+
url = f"{base_url}/country/{country}/group/{group}?c="
|
| 65 |
+
# Country + Group + Date
|
| 66 |
+
elif check_args(query, ["country", "group", "start_date", "end_date"]):
|
| 67 |
+
url = f'{base_url}/country/{country}/group/{group}/{query["start_date"]}/{query["end_date"]}?c='
|
| 68 |
+
# Country + Date + Importance
|
| 69 |
+
elif check_args(query, ["country", "importance", "start_date", "end_date"]):
|
| 70 |
+
url = f'{base_url}/country/{country}/{query["start_date"]}/{query["end_date"]}?{urlencode(query)}&c='
|
| 71 |
+
# By date only
|
| 72 |
+
elif check_args(query, ["start_date", "end_date"]):
|
| 73 |
+
url = f'{base_url}/country/All/{query["start_date"]}/{query["end_date"]}?c='
|
| 74 |
+
# By importance only
|
| 75 |
+
elif check_args(query, ["importance"]):
|
| 76 |
+
url = f"{base_url}?{urlencode(query)}&c="
|
| 77 |
+
# By importance and date
|
| 78 |
+
elif check_args(query, ["importance", "start_date", "end_date"]):
|
| 79 |
+
url = f'{base_url}/country/All/{query["start_date"]}/{query["end_date"]}?{urlencode(query)}&c='
|
| 80 |
+
# Group Only
|
| 81 |
+
elif check_args(query, ["group"]):
|
| 82 |
+
url = f'{base_url}/group/{query["group"]}?c='
|
| 83 |
+
# Group + Date
|
| 84 |
+
elif check_args(query, ["group", "start_date", "end_date"]):
|
| 85 |
+
url = f'{base_url}/group/{query["group"]}/{query["start_date"]}/{query["end_date"]}?c='
|
| 86 |
+
# All fields
|
| 87 |
+
elif check_args(
|
| 88 |
+
query, ["country", "group", "importance", "start_date", "end_date"]
|
| 89 |
+
):
|
| 90 |
+
start_date = query["start_date"]
|
| 91 |
+
end_date = query["end_date"]
|
| 92 |
+
url = f"{base_url}/country/{country}/group/{group}/{start_date}/{end_date}?{urlencode(query)}&c="
|
| 93 |
+
# Calendar IDs
|
| 94 |
+
elif check_args(query, ["calendar_id"]):
|
| 95 |
+
url = f'{base_url}/calendarid/{str(query["calendar_id"])}?c='
|
| 96 |
+
|
| 97 |
+
return url if url else ""
|
openbb_platform/providers/tradingeconomics/poetry.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
openbb_platform/providers/tradingeconomics/pyproject.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[tool.poetry]
|
| 2 |
+
name = "openbb-tradingeconomics"
|
| 3 |
+
version = "1.4.1"
|
| 4 |
+
description = "Trading Economics extension for OpenBB"
|
| 5 |
+
authors = ["OpenBB Team <hello@openbb.co>"]
|
| 6 |
+
license = "AGPL-3.0-only"
|
| 7 |
+
readme = "README.md"
|
| 8 |
+
packages = [{ include = "openbb_tradingeconomics" }]
|
| 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_provider_extension"]
|
| 19 |
+
tradingeconomics = "openbb_tradingeconomics:tradingeconomics_provider"
|
openbb_platform/providers/tradingeconomics/tests/record/http/test_tradingeconomics_fetchers/test_tradingeconomics_economic_calendar_fetcher_urllib3_v1.yaml
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
openbb_platform/providers/tradingeconomics/tests/record/http/test_tradingeconomics_fetchers/test_tradingeconomics_economic_calendar_fetcher_urllib3_v2.yaml
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
openbb_platform/providers/tradingeconomics/tests/test_tradingeconomics_fetchers.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Test the Trading Economics fetchers."""
|
| 2 |
+
|
| 3 |
+
from datetime import date
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
from openbb_core.app.service.user_service import UserService
|
| 7 |
+
from openbb_tradingeconomics.models.economic_calendar import TEEconomicCalendarFetcher
|
| 8 |
+
|
| 9 |
+
test_credentials = UserService().default_user_settings.credentials.model_dump(
|
| 10 |
+
mode="json"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@pytest.fixture(scope="module")
|
| 15 |
+
def vcr_config():
|
| 16 |
+
"""VCR configuration."""
|
| 17 |
+
return {
|
| 18 |
+
"filter_headers": [("User-Agent", None)],
|
| 19 |
+
"filter_query_parameters": [
|
| 20 |
+
("c", "mock_api_key"),
|
| 21 |
+
],
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@pytest.mark.record_http
|
| 26 |
+
def test_tradingeconomics_economic_calendar_fetcher(credentials=test_credentials):
|
| 27 |
+
"""Test the Trading Economics economic calendar fetcher."""
|
| 28 |
+
params = {
|
| 29 |
+
"start_date": date(2023, 1, 1),
|
| 30 |
+
"end_date": date(2023, 6, 6),
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
fetcher = TEEconomicCalendarFetcher()
|
| 34 |
+
result = fetcher.test(params, credentials)
|
| 35 |
+
assert result is None
|
openbb_platform/providers/tradingeconomics/tests/test_url_generator.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Test the url generator."""
|
| 2 |
+
|
| 3 |
+
from unittest.mock import Mock
|
| 4 |
+
|
| 5 |
+
from providers.tradingeconomics.openbb_tradingeconomics.utils.url_generator import (
|
| 6 |
+
check_args,
|
| 7 |
+
generate_url,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_check_args_valid():
|
| 12 |
+
"""Test check_args with valid args."""
|
| 13 |
+
query_args = {"country": "US", "start_date": "2023-01-01", "end_date": "2023-01-31"}
|
| 14 |
+
to_include = ["country", "start_date", "end_date"]
|
| 15 |
+
assert check_args(query_args, to_include)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_check_args_invalid():
|
| 19 |
+
"""Test check_args with missing args."""
|
| 20 |
+
query_args = {"country": "US"}
|
| 21 |
+
to_include = ["start_date", "end_date"]
|
| 22 |
+
assert not check_args(query_args, to_include)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def create_query_mock(query_dict):
|
| 26 |
+
"""Create a query mock."""
|
| 27 |
+
query_mock = Mock()
|
| 28 |
+
query_mock.dict.return_value = query_dict
|
| 29 |
+
return query_mock
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def test_generate_url_country_only():
|
| 33 |
+
"""Test generate_url with country."""
|
| 34 |
+
query_mock = create_query_mock({"country": "US"})
|
| 35 |
+
expected_url = "https://api.tradingeconomics.com/calendar/country/US?c="
|
| 36 |
+
assert generate_url(query_mock) == expected_url
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def test_generate_url_country_and_dates():
|
| 40 |
+
"""Test generate_url with country and dates."""
|
| 41 |
+
query_mock = create_query_mock(
|
| 42 |
+
{"country": "US", "start_date": "2023-01-01", "end_date": "2023-01-31"}
|
| 43 |
+
)
|
| 44 |
+
expected_url = (
|
| 45 |
+
"https://api.tradingeconomics.com/calendar/country/US/2023-01-01/2023-01-31?c="
|
| 46 |
+
)
|
| 47 |
+
assert generate_url(query_mock) == expected_url
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_generate_url_importance_only():
|
| 51 |
+
"""Test generate_url with importance."""
|
| 52 |
+
query_mock = create_query_mock({"importance": "High"})
|
| 53 |
+
expected_url = "https://api.tradingeconomics.com/calendar?importance=High&c="
|
| 54 |
+
assert generate_url(query_mock) == expected_url
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def test_generate_url_no_data():
|
| 58 |
+
"""Test generate_url with no data."""
|
| 59 |
+
query_mock = create_query_mock({})
|
| 60 |
+
expected_url = "https://api.tradingeconomics.com/calendar?c="
|
| 61 |
+
assert generate_url(query_mock) == expected_url
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def test_generate_url_group_only():
|
| 65 |
+
"""Test generate_url with group."""
|
| 66 |
+
query_mock = create_query_mock({"group": "G20"})
|
| 67 |
+
expected_url = "https://api.tradingeconomics.com/calendar/group/G20?c="
|
| 68 |
+
assert generate_url(query_mock) == expected_url
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def test_generate_url_country_group_and_dates():
|
| 72 |
+
"""Test generate_url with country, group, and dates."""
|
| 73 |
+
query_mock = create_query_mock(
|
| 74 |
+
{
|
| 75 |
+
"country": "US",
|
| 76 |
+
"group": "G20",
|
| 77 |
+
"start_date": "2023-01-01",
|
| 78 |
+
"end_date": "2023-01-31",
|
| 79 |
+
}
|
| 80 |
+
)
|
| 81 |
+
expected_url = "https://api.tradingeconomics.com/calendar/country/US/group/G20/2023-01-01/2023-01-31?c="
|
| 82 |
+
assert generate_url(query_mock) == expected_url
|