CatPtain commited on
Commit
8ae3767
·
verified ·
1 Parent(s): b18eaa6

Upload 36 files

Browse files
Files changed (36) hide show
  1. openbb_platform/providers/tradier/README.md +24 -0
  2. openbb_platform/providers/tradier/__init__.py +1 -0
  3. openbb_platform/providers/tradier/openbb_tradier/__init__.py +31 -0
  4. openbb_platform/providers/tradier/openbb_tradier/models/__init__.py +1 -0
  5. openbb_platform/providers/tradier/openbb_tradier/models/equity_historical.py +195 -0
  6. openbb_platform/providers/tradier/openbb_tradier/models/equity_quote.py +294 -0
  7. openbb_platform/providers/tradier/openbb_tradier/models/equity_search.py +130 -0
  8. openbb_platform/providers/tradier/openbb_tradier/models/options_chains.py +263 -0
  9. openbb_platform/providers/tradier/openbb_tradier/py.typed +0 -0
  10. openbb_platform/providers/tradier/openbb_tradier/utils/__init__.py +1 -0
  11. openbb_platform/providers/tradier/openbb_tradier/utils/constants.py +53 -0
  12. openbb_platform/providers/tradier/poetry.lock +0 -0
  13. openbb_platform/providers/tradier/pyproject.toml +19 -0
  14. openbb_platform/providers/tradier/tests/__init__.py +1 -0
  15. openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_derivatives_options_chains_fetcher_urllib3_v1.yaml +0 -0
  16. openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_derivatives_options_chains_fetcher_urllib3_v2.yaml +0 -0
  17. openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_historical_fetcher_urllib3_v1.yaml +40 -0
  18. openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_historical_fetcher_urllib3_v2.yaml +40 -0
  19. openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_quote_fetcher_urllib3_v1.yaml +42 -0
  20. openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_quote_fetcher_urllib3_v2.yaml +42 -0
  21. openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_search_fetcher_urllib3_v1.yaml +124 -0
  22. openbb_platform/providers/tradier/tests/record/http/test_tradier_fetchers/test_tradier_equity_search_fetcher_urllib3_v2.yaml +132 -0
  23. openbb_platform/providers/tradier/tests/test_tradier_fetchers.py +75 -0
  24. openbb_platform/providers/tradingeconomics/README.md +13 -0
  25. openbb_platform/providers/tradingeconomics/__init__.py +1 -0
  26. openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/__init__.py +19 -0
  27. openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/models/economic_calendar.py +270 -0
  28. openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/countries.py +235 -0
  29. openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/helpers.py +26 -0
  30. openbb_platform/providers/tradingeconomics/openbb_tradingeconomics/utils/url_generator.py +97 -0
  31. openbb_platform/providers/tradingeconomics/poetry.lock +0 -0
  32. openbb_platform/providers/tradingeconomics/pyproject.toml +19 -0
  33. openbb_platform/providers/tradingeconomics/tests/record/http/test_tradingeconomics_fetchers/test_tradingeconomics_economic_calendar_fetcher_urllib3_v1.yaml +0 -0
  34. openbb_platform/providers/tradingeconomics/tests/record/http/test_tradingeconomics_fetchers/test_tradingeconomics_economic_calendar_fetcher_urllib3_v2.yaml +0 -0
  35. openbb_platform/providers/tradingeconomics/tests/test_tradingeconomics_fetchers.py +35 -0
  36. 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![Tradier](https://user-images.githubusercontent.com/46355364/207829178-a8bba770-f2ea-4480-b28e-efd81cf30980.png)\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