mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-06 09:22:26 +00:00
Merge branch 'develop' into feature/argparse_translator
This commit is contained in:
@@ -5,7 +5,7 @@ from datetime import (
|
||||
)
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic import Field
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
@@ -67,15 +67,6 @@ class BondPricesQueryParams(QueryParams):
|
||||
description="Minimum yield to maturity of the bond.",
|
||||
)
|
||||
|
||||
@field_validator("isin", "currency", "lei", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def validate_upper_case(cls, v):
|
||||
"""Convert the field to uppercase and convert a list to a query string."""
|
||||
if isinstance(v, str):
|
||||
return v.upper()
|
||||
v = ",".join([symbol.upper() for symbol in list(v)])
|
||||
return v if v else None
|
||||
|
||||
|
||||
class BondPricesData(Data):
|
||||
"""Bond Prices Data."""
|
||||
|
||||
@@ -65,8 +65,7 @@ class BondReferenceQueryParams(QueryParams):
|
||||
"""Convert the field to uppercase and convert a list to a query string."""
|
||||
if isinstance(v, str):
|
||||
return v.upper()
|
||||
v = ",".join([symbol.upper() for symbol in list(v)])
|
||||
return v if v else None
|
||||
return ",".join([symbol.upper() for symbol in list(v)]) if v else None
|
||||
|
||||
|
||||
class BondReferenceData(Data):
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
"""Index Constituents Standard Model."""
|
||||
|
||||
from typing import List, Optional, Set, Union
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic import Field
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
|
||||
from openbb_core.provider.utils.descriptions import (
|
||||
DATA_DESCRIPTIONS,
|
||||
QUERY_DESCRIPTIONS,
|
||||
)
|
||||
|
||||
|
||||
class IndexConstituentsQueryParams(QueryParams):
|
||||
"""Index Constituents Query."""
|
||||
|
||||
index: str = Field(
|
||||
description="Index to fetch the constituents of.",
|
||||
)
|
||||
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", ""))
|
||||
|
||||
|
||||
class IndexConstituentsData(Data):
|
||||
@@ -24,11 +25,3 @@ class IndexConstituentsData(Data):
|
||||
name: Optional[str] = Field(
|
||||
default=None, description="Name of the constituent company in the index."
|
||||
)
|
||||
|
||||
@field_validator("symbol", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def upper_symbol(cls, v: Union[str, List[str], Set[str]]):
|
||||
"""Convert symbol to uppercase."""
|
||||
if isinstance(v, str):
|
||||
return v.upper()
|
||||
return ",".join([symbol.upper() for symbol in list(v)])
|
||||
|
||||
@@ -12,8 +12,8 @@ from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
|
||||
class IndexSnapshotsQueryParams(QueryParams):
|
||||
"""Index Snapshots Query."""
|
||||
|
||||
region: Optional[str] = Field(
|
||||
default=None, description="The region to return data for - i.e. 'us' or 'eu'."
|
||||
region: str = Field(
|
||||
default="us", description="The region of focus for the data - i.e., us, eu."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ class InsiderTradingQueryParams(QueryParams):
|
||||
class InsiderTradingData(Data):
|
||||
"""Insider Trading Data."""
|
||||
|
||||
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
|
||||
symbol: Optional[str] = Field(
|
||||
default=None, description=DATA_DESCRIPTIONS.get("symbol", "")
|
||||
)
|
||||
company_cik: Optional[Union[int, str]] = Field(
|
||||
default=None, description="CIK number of the company."
|
||||
)
|
||||
|
||||
@@ -7,14 +7,11 @@ from pydantic import Field
|
||||
|
||||
from openbb_core.provider.abstract.data import Data
|
||||
from openbb_core.provider.abstract.query_params import QueryParams
|
||||
from openbb_core.provider.utils.descriptions import (
|
||||
DATA_DESCRIPTIONS,
|
||||
QUERY_DESCRIPTIONS,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS
|
||||
|
||||
|
||||
class USTreasuryPricesQueryParams(QueryParams):
|
||||
"""US Treasury Prices Query."""
|
||||
class TreasuryPricesQueryParams(QueryParams):
|
||||
"""Treasury Prices Query."""
|
||||
|
||||
date: Optional[dateType] = Field(
|
||||
description=QUERY_DESCRIPTIONS.get("date", "")
|
||||
@@ -23,19 +20,33 @@ class USTreasuryPricesQueryParams(QueryParams):
|
||||
)
|
||||
|
||||
|
||||
class USTreasuryPricesData(Data):
|
||||
"""US Treasury Prices Data."""
|
||||
class TreasuryPricesData(Data):
|
||||
"""Treasury Prices Data."""
|
||||
|
||||
date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
|
||||
cusip: str = Field(description="CUSIP of the security.")
|
||||
security_type: str = Field(
|
||||
description="The type of US Treasury security. Bill, Note, Bond, TIPS, FRN."
|
||||
)
|
||||
rate: Optional[float] = Field(
|
||||
description="The annualized interest rate or coupon of the security.",
|
||||
issuer_name: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Name of the issuing entity.",
|
||||
)
|
||||
cusip: Optional[str] = Field(
|
||||
default=None,
|
||||
description="CUSIP of the security.",
|
||||
)
|
||||
isin: Optional[str] = Field(
|
||||
default=None,
|
||||
description="ISIN of the security.",
|
||||
)
|
||||
security_type: Optional[str] = Field(
|
||||
default=None,
|
||||
description="The type of Treasury security - i.e., Bill, Note, Bond, TIPS, FRN.",
|
||||
)
|
||||
issue_date: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="The original issue date of the security.",
|
||||
)
|
||||
maturity_date: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="The maturity date of the security.",
|
||||
)
|
||||
maturity_date: dateType = Field(description="The maturity date of the security.")
|
||||
call_date: Optional[dateType] = Field(
|
||||
description="The call date of the security.", default=None
|
||||
)
|
||||
@@ -48,3 +59,32 @@ class USTreasuryPricesData(Data):
|
||||
eod_price: Optional[float] = Field(
|
||||
description="The end-of-day price of the security.", default=None
|
||||
)
|
||||
last_traded_date: Optional[dateType] = Field(
|
||||
description="The last trade date of the security.", default=None
|
||||
)
|
||||
total_trades: Optional[int] = Field(
|
||||
default=None,
|
||||
description="Total number of trades on the last traded date.",
|
||||
)
|
||||
last_price: Optional[float] = Field(
|
||||
description="The last price of the security.", default=None
|
||||
)
|
||||
highest_price: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The highest price for the bond on the last traded date.",
|
||||
)
|
||||
lowest_price: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The lowest price for the bond on the last traded date.",
|
||||
)
|
||||
rate: Optional[float] = Field(
|
||||
description="The annualized interest rate or coupon of the security.",
|
||||
default=None,
|
||||
)
|
||||
ytm: Optional[float] = Field(
|
||||
default=None,
|
||||
description="Yield to maturity (YTM) is the rate of return anticipated on a bond"
|
||||
+ " if it is held until the maturity date. It takes into account"
|
||||
+ " the current market price, par value, coupon rate and time to maturity. It is assumed that all"
|
||||
+ " coupons are reinvested at the same rate.",
|
||||
)
|
||||
|
||||
@@ -52,6 +52,7 @@ openbb-government-us = { path = "./providers/government_us", optional = true, de
|
||||
openbb-nasdaq = { path = "./providers/nasdaq", optional = true, develop = true }
|
||||
openbb-seeking-alpha = { path = "./providers/seeking_alpha", optional = true, develop = true }
|
||||
openbb-stockgrid = { path = "./providers/stockgrid" , optional = true, develop = true }
|
||||
openbb_tmx = { path = "./providers/tmx", optional = true, develop = true }
|
||||
openbb-wsj = { path = "./providers/wsj", optional = true, develop = true }
|
||||
|
||||
openbb-charting = { path = "./obbject_extensions/charting", optional = true, develop = true }
|
||||
|
||||
@@ -25,6 +25,14 @@ def headers():
|
||||
[
|
||||
({"provider": "intrinio", "symbol": "AAPL", "date": "2023-01-25"}),
|
||||
({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"symbol": "SHOP",
|
||||
"date": "2022-12-28",
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
|
||||
@@ -22,6 +22,14 @@ def obb(pytestconfig):
|
||||
[
|
||||
({"provider": "intrinio", "symbol": "AAPL", "date": "2023-01-25"}),
|
||||
({"provider": "cboe", "symbol": "AAPL", "use_cache": False}),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"symbol": "SHOP",
|
||||
"date": "2022-12-28",
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
|
||||
@@ -139,6 +139,7 @@ def test_equity_calendar_splits(params, headers):
|
||||
[
|
||||
({"start_date": "2023-11-09", "end_date": "2023-11-10", "provider": "fmp"}),
|
||||
({"start_date": "2023-11-09", "end_date": "2023-11-10", "provider": "nasdaq"}),
|
||||
({"start_date": "2023-11-09", "end_date": "2023-11-10", "provider": "tmx"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -292,6 +293,7 @@ def test_equity_fundamental_historical_splits(params, headers):
|
||||
"provider": "intrinio",
|
||||
}
|
||||
),
|
||||
({"symbol": "RY", "provider": "tmx"}),
|
||||
(
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
@@ -451,6 +453,13 @@ def test_equity_fundamental_income_growth(params, headers):
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
(
|
||||
{
|
||||
"symbol": "RY",
|
||||
"provider": "tmx",
|
||||
"limit": None,
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "fmp",
|
||||
@@ -693,6 +702,7 @@ def test_equity_estimates_analyst_search(params, headers):
|
||||
[
|
||||
({"symbol": "AAPL", "provider": "fmp"}),
|
||||
({"symbol": "AAPL,AMZN,RELIANCE.NS", "provider": "yfinance"}),
|
||||
({"symbol": "TD:US", "provider": "tmx"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -799,6 +809,16 @@ def test_equity_fundamental_revenue_per_segment(params, headers):
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"symbol": "IBM:US",
|
||||
"start_date": "2023-09-30",
|
||||
"end_date": "2023-12-31",
|
||||
"limit": None,
|
||||
"form_type": None,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -1048,6 +1068,16 @@ def test_equity_compare_groups(params, headers):
|
||||
"interval": "1M",
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"symbol": "AAPL:US",
|
||||
"start_date": "2023-01-01",
|
||||
"end_date": "2023-12-31",
|
||||
"interval": "1d",
|
||||
"adjustment": "splits_only",
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -1241,6 +1271,7 @@ def test_equity_fundamental_latest_attributes(params, headers):
|
||||
({"query": "AAPl", "is_symbol": True, "provider": "cboe", "use_cache": False}),
|
||||
({"query": "Apple", "provider": "sec", "use_cache": False, "is_fund": False}),
|
||||
({"query": "", "provider": "nasdaq", "use_cache": False, "is_etf": True}),
|
||||
({"query": "gold", "provider": "tmx", "use_cache": False}),
|
||||
(
|
||||
{
|
||||
"query": "gold",
|
||||
@@ -1308,6 +1339,7 @@ def test_equity_screener(params, headers):
|
||||
({"symbol": "AAPL", "provider": "fmp"}),
|
||||
({"symbol": "AAPL", "provider": "cboe", "use_cache": False}),
|
||||
({"symbol": "AAPL", "provider": "yfinance"}),
|
||||
({"symbol": "AAPL:US", "provider": "tmx"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -1328,6 +1360,7 @@ def test_equity_price_quote(params, headers):
|
||||
({"symbol": "AAPL,MSFT", "provider": "intrinio"}),
|
||||
({"symbol": "AAPL,MSFT", "provider": "finviz"}),
|
||||
({"symbol": "AAPL,MSFT", "provider": "yfinance"}),
|
||||
({"provider": "tmx", "symbol": "AAPL:US"}),
|
||||
({"symbol": "AAPL,MSFT", "provider": "fmp"}),
|
||||
],
|
||||
)
|
||||
@@ -1344,7 +1377,10 @@ def test_equity_profile(params, headers):
|
||||
|
||||
@parametrize(
|
||||
"params",
|
||||
[({"sort": "desc", "provider": "yfinance"})],
|
||||
[
|
||||
({"sort": "desc", "provider": "yfinance"}),
|
||||
({"provider": "tmx", "category": "52w_high"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_equity_discovery_gainers(params, headers):
|
||||
|
||||
@@ -127,6 +127,7 @@ def test_equity_calendar_splits(params, obb):
|
||||
[
|
||||
({"start_date": "2023-11-09", "end_date": "2023-11-10", "provider": "fmp"}),
|
||||
({"start_date": "2023-11-09", "end_date": "2023-11-10", "provider": "nasdaq"}),
|
||||
({"start_date": "2023-11-09", "end_date": "2023-11-10", "provider": "tmx"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -269,6 +270,7 @@ def test_equity_fundamental_historical_splits(params, obb):
|
||||
"provider": "intrinio",
|
||||
}
|
||||
),
|
||||
({"symbol": "RY", "provider": "tmx"}),
|
||||
(
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
@@ -419,8 +421,9 @@ def test_equity_fundamental_income_growth(params, obb):
|
||||
[
|
||||
(
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
"limit": 10,
|
||||
"symbol": "RY",
|
||||
"provider": "tmx",
|
||||
"limit": None,
|
||||
}
|
||||
),
|
||||
(
|
||||
@@ -646,6 +649,7 @@ def test_equity_estimates_analyst_search(params, obb):
|
||||
[
|
||||
({"symbol": "AAPL", "provider": "fmp"}),
|
||||
({"symbol": "AAPL,AMZN,RELIANCE.NS", "provider": "yfinance"}),
|
||||
({"symbol": "TD:US", "provider": "tmx"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -756,6 +760,16 @@ def test_equity_fundamental_revenue_per_segment(params, obb):
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"symbol": "IBM:US",
|
||||
"start_date": "2023-09-30",
|
||||
"end_date": "2023-12-31",
|
||||
"limit": None,
|
||||
"form_type": None,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -993,6 +1007,16 @@ def test_equity_compare_groups(params, obb):
|
||||
"interval": "1M",
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"symbol": "AAPL:US",
|
||||
"start_date": "2023-01-01",
|
||||
"end_date": "2023-12-31",
|
||||
"interval": "1d",
|
||||
"adjustment": "splits_only",
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -1176,6 +1200,7 @@ def test_equity_fundamental_latest_attributes(params, obb):
|
||||
({"query": "AAPL", "is_symbol": True, "provider": "cboe", "use_cache": False}),
|
||||
({"query": "Apple", "provider": "sec", "use_cache": False, "is_fund": False}),
|
||||
({"query": "", "provider": "nasdaq", "use_cache": False, "is_etf": True}),
|
||||
({"query": "gold", "provider": "tmx", "use_cache": False}),
|
||||
(
|
||||
{
|
||||
"query": "gold",
|
||||
@@ -1238,6 +1263,7 @@ def test_equity_screener(params, obb):
|
||||
({"symbol": "AAPL", "provider": "fmp"}),
|
||||
({"symbol": "AAPL", "provider": "cboe", "use_cache": False}),
|
||||
({"symbol": "AAPL", "provider": "yfinance"}),
|
||||
({"symbol": "AAPL:US", "provider": "tmx"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -1256,6 +1282,7 @@ def test_equity_price_quote(params, obb):
|
||||
({"symbol": "AAPL,MSFT", "provider": "finviz"}),
|
||||
({"symbol": "AAPL,MSFT", "provider": "yfinance"}),
|
||||
({"symbol": "AAPL,MSFT", "provider": "fmp"}),
|
||||
({"provider": "tmx", "symbol": "AAPL:US"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -1271,7 +1298,10 @@ def test_equity_profile(params, obb):
|
||||
|
||||
@parametrize(
|
||||
"params",
|
||||
[({"sort": "desc"})],
|
||||
[
|
||||
({"sort": "desc", "provider": "yfinance"}),
|
||||
({"provider": "tmx", "category": "52w_high"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_equity_discovery_gainers(params, obb):
|
||||
|
||||
@@ -23,6 +23,15 @@ def headers():
|
||||
"params",
|
||||
[
|
||||
({"query": "", "provider": "fmp"}),
|
||||
(
|
||||
{
|
||||
"query": "vanguard",
|
||||
"provider": "tmx",
|
||||
"div_freq": "quarterly",
|
||||
"sort_by": "return_1y",
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -72,7 +81,7 @@ def test_etf_historical(params, headers):
|
||||
"params",
|
||||
[
|
||||
({"symbol": "IOO", "provider": "fmp"}),
|
||||
({"symbol": "MISL", "provider": "fmp"}),
|
||||
({"symbol": "XIU", "provider": "tmx", "use_cache": False}),
|
||||
({"symbol": "QQQ", "provider": "yfinance"}),
|
||||
],
|
||||
)
|
||||
@@ -91,7 +100,7 @@ def test_etf_info(params, headers):
|
||||
"params",
|
||||
[
|
||||
({"symbol": "IOO", "provider": "fmp"}),
|
||||
({"symbol": "MISL", "provider": "fmp"}),
|
||||
({"symbol": "XIU", "provider": "tmx", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -157,6 +166,13 @@ def test_etf_holdings_date(params, headers):
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"symbol": "XIU",
|
||||
"provider": "tmx",
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -190,7 +206,10 @@ def test_etf_price_performance(params, headers):
|
||||
|
||||
@parametrize(
|
||||
"params",
|
||||
[({"symbol": "IOO"})],
|
||||
[
|
||||
({"symbol": "IOO", "provider": "fmp"}),
|
||||
({"symbol": "XIU", "use_cache": False, "provider": "tmx"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_etf_countries(params, headers):
|
||||
|
||||
@@ -22,6 +22,15 @@ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
|
||||
"params",
|
||||
[
|
||||
({"query": None, "provider": "fmp"}),
|
||||
(
|
||||
{
|
||||
"query": "vanguard",
|
||||
"provider": "tmx",
|
||||
"div_freq": "quarterly",
|
||||
"sort_by": "return_1y",
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -69,7 +78,7 @@ def test_etf_historical(params, obb):
|
||||
"params",
|
||||
[
|
||||
({"symbol": "IOO", "provider": "fmp"}),
|
||||
({"symbol": "MISL", "provider": "fmp"}),
|
||||
({"symbol": "XIU", "provider": "tmx", "use_cache": False}),
|
||||
({"symbol": "QQQ", "provider": "yfinance"}),
|
||||
],
|
||||
)
|
||||
@@ -87,7 +96,7 @@ def test_etf_info(params, obb):
|
||||
"params",
|
||||
[
|
||||
({"symbol": "IOO", "provider": "fmp"}),
|
||||
({"symbol": "MISL", "provider": "fmp"}),
|
||||
({"symbol": "XIU", "provider": "tmx", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -151,6 +160,13 @@ def test_etf_holdings_date(params, obb):
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"symbol": "XIU",
|
||||
"provider": "tmx",
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -182,7 +198,10 @@ def test_etf_price_performance(params, obb):
|
||||
|
||||
@parametrize(
|
||||
"params",
|
||||
[({"symbol": "IOO"})],
|
||||
[
|
||||
({"symbol": "IOO", "provider": "fmp"}),
|
||||
({"symbol": "XIU", "provider": "tmx", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_etf_countries(params, obb):
|
||||
|
||||
@@ -155,7 +155,6 @@ def test_fixedincome_rate_ameribor(params, headers):
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
({"start_date": "2023-01-01", "end_date": "2023-06-06", "provider": "fred"}),
|
||||
(
|
||||
{
|
||||
"parameter": "weekly",
|
||||
@@ -515,12 +514,25 @@ def test_fixedincome_government_treasury_auctions(params, headers):
|
||||
),
|
||||
(
|
||||
{
|
||||
"date": "2023-11-16",
|
||||
"date": "2023-12-28",
|
||||
"cusip": None,
|
||||
"security_type": "bill",
|
||||
"provider": "government_us",
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"date": None,
|
||||
"provider": "tmx",
|
||||
"govt_type": "federal",
|
||||
"issue_date_min": None,
|
||||
"issue_date_max": None,
|
||||
"last_traded_min": None,
|
||||
"maturity_date_min": None,
|
||||
"maturity_date_max": None,
|
||||
"use_cache": True,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -534,3 +546,39 @@ def test_fixedincome_government_treasury_prices(params, headers):
|
||||
result = requests.get(url, headers=headers, timeout=30)
|
||||
assert isinstance(result, requests.Response)
|
||||
assert result.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"params",
|
||||
[
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"issuer_name": "federal",
|
||||
"issue_date_min": None,
|
||||
"issue_date_max": None,
|
||||
"last_traded_min": None,
|
||||
"coupon_min": 3,
|
||||
"coupon_max": None,
|
||||
"currency": None,
|
||||
"issued_amount_min": None,
|
||||
"issued_amount_max": None,
|
||||
"maturity_date_min": None,
|
||||
"maturity_date_max": None,
|
||||
"isin": None,
|
||||
"lei": None,
|
||||
"country": None,
|
||||
"use_cache": False,
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_fixedincome_corporate_bond_prices(params, headers):
|
||||
params = {p: v for p, v in params.items() if v}
|
||||
|
||||
query_str = get_querystring(params, [])
|
||||
url = f"http://0.0.0.0:8000/api/v1/fixedincome/corporate/bond_prices?{query_str}"
|
||||
result = requests.get(url, headers=headers, timeout=30)
|
||||
assert isinstance(result, requests.Response)
|
||||
assert result.status_code == 200
|
||||
|
||||
@@ -137,7 +137,6 @@ def test_fixedincome_rate_ameribor(params, obb):
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
({"start_date": "2023-01-01", "end_date": "2023-06-06"}),
|
||||
(
|
||||
{
|
||||
"parameter": "weekly",
|
||||
@@ -477,12 +476,25 @@ def test_fixedincome_government_treasury_auctions(params, obb):
|
||||
),
|
||||
(
|
||||
{
|
||||
"date": "2023-11-16",
|
||||
"date": "2023-12-28",
|
||||
"cusip": None,
|
||||
"security_type": "bill",
|
||||
"provider": "government_us",
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"date": None,
|
||||
"provider": "tmx",
|
||||
"govt_type": "federal",
|
||||
"issue_date_min": None,
|
||||
"issue_date_max": None,
|
||||
"last_traded_min": None,
|
||||
"maturity_date_min": None,
|
||||
"maturity_date_max": None,
|
||||
"use_cache": True,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -493,3 +505,38 @@ def test_fixedincome_government_treasury_prices(params, obb):
|
||||
assert result
|
||||
assert isinstance(result, OBBject)
|
||||
assert len(result.results) > 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"params",
|
||||
[
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"issuer_name": "federal",
|
||||
"issue_date_min": None,
|
||||
"issue_date_max": None,
|
||||
"last_traded_min": None,
|
||||
"coupon_min": 3,
|
||||
"coupon_max": None,
|
||||
"currency": None,
|
||||
"issued_amount_min": None,
|
||||
"issued_amount_max": None,
|
||||
"maturity_date_min": None,
|
||||
"maturity_date_max": None,
|
||||
"isin": None,
|
||||
"lei": None,
|
||||
"country": None,
|
||||
"use_cache": False,
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_fixedincome_corporate_bond_prices(params, obb):
|
||||
params = {p: v for p, v in params.items() if v}
|
||||
|
||||
result = obb.fixedincome.corporate.bond_prices(**params)
|
||||
assert result
|
||||
assert isinstance(result, OBBject)
|
||||
assert len(result.results) > 0
|
||||
|
||||
@@ -127,3 +127,14 @@ async def commercial_paper(
|
||||
and many find it to be a lower-cost alternative to bank loans.
|
||||
"""
|
||||
return await OBBject.from_query(Query(**locals()))
|
||||
|
||||
|
||||
@router.command(model="BondPrices")
|
||||
async def bond_prices(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Corporate Bond Prices."""
|
||||
return await OBBject.from_query(Query(**locals()))
|
||||
|
||||
@@ -22,8 +22,9 @@ def headers():
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
({"index": "dowjones", "provider": "fmp"}),
|
||||
({"index": "BUKBUS", "provider": "cboe"}),
|
||||
({"symbol": "dowjones", "provider": "fmp"}),
|
||||
({"symbol": "^TX60", "provider": "tmx", "use_cache": False}),
|
||||
({"symbol": "BUKBUS", "provider": "cboe"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -284,6 +285,7 @@ def test_index_market(params, headers):
|
||||
({"provider": "cboe", "use_cache": False}),
|
||||
({"provider": "fmp"}),
|
||||
({"provider": "yfinance"}),
|
||||
({"provider": "tmx", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -316,7 +318,10 @@ def test_index_search(params, headers):
|
||||
|
||||
@parametrize(
|
||||
"params",
|
||||
[({"provider": "cboe", "region": "us"})],
|
||||
[
|
||||
({"provider": "cboe", "region": "us"}),
|
||||
({"provider": "tmx", "region": "ca", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_index_snapshots(params, headers):
|
||||
@@ -353,3 +358,20 @@ def test_index_sp500_multiples(params, headers):
|
||||
result = requests.get(url, headers=headers, timeout=20)
|
||||
assert isinstance(result, requests.Response)
|
||||
assert result.status_code == 200
|
||||
|
||||
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
({"provider": "tmx", "symbol": "^TX60", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_index_sectors(params, headers):
|
||||
params = {p: v for p, v in params.items() if v}
|
||||
|
||||
query_str = get_querystring(params, [])
|
||||
url = f"http://0.0.0.0:8000/api/v1/index/sectors?{query_str}"
|
||||
result = requests.get(url, headers=headers, timeout=10)
|
||||
assert isinstance(result, requests.Response)
|
||||
assert result.status_code == 200
|
||||
|
||||
@@ -21,8 +21,9 @@ def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
({"index": "dowjones", "provider": "fmp"}),
|
||||
({"index": "BUKBUS", "provider": "cboe"}),
|
||||
({"symbol": "dowjones", "provider": "fmp"}),
|
||||
({"symbol": "BUKBUS", "provider": "cboe"}),
|
||||
({"symbol": "^TX60", "provider": "tmx", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -275,6 +276,7 @@ def test_index_market(params, obb):
|
||||
({"provider": "cboe", "use_cache": False}),
|
||||
({"provider": "fmp"}),
|
||||
({"provider": "yfinance"}),
|
||||
({"provider": "tmx", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -288,7 +290,14 @@ def test_index_available(params, obb):
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
({"query": "D", "is_symbol": True, "use_cache": False, "provider": "cboe"}),
|
||||
(
|
||||
{
|
||||
"query": "D",
|
||||
"is_symbol": True,
|
||||
"provider": "cboe",
|
||||
"use_cache": False,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -303,6 +312,7 @@ def test_index_search(params, obb):
|
||||
"params",
|
||||
[
|
||||
({"region": "us", "provider": "cboe"}),
|
||||
({"provider": "tmx", "region": "ca", "use_cache": False}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
@@ -334,3 +344,17 @@ def test_index_sp500_multiples(params, obb):
|
||||
assert result
|
||||
assert isinstance(result, OBBject)
|
||||
assert len(result.results) > 0
|
||||
|
||||
|
||||
@parametrize(
|
||||
"params",
|
||||
[
|
||||
({"symbol": "^TX60", "provider": "tmx"}),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
def test_index_sectors(params, obb):
|
||||
result = obb.index.sectors(**params)
|
||||
assert result
|
||||
assert isinstance(result, OBBject)
|
||||
assert len(result.results) > 0
|
||||
|
||||
@@ -123,3 +123,20 @@ async def sp500_multiples(
|
||||
) -> OBBject:
|
||||
"""Historical S&P 500 multiples and Shiller PE ratios."""
|
||||
return await OBBject.from_query(Query(**locals()))
|
||||
|
||||
|
||||
@router.command(
|
||||
model="IndexSectors",
|
||||
exclude_auto_examples=True,
|
||||
examples=[
|
||||
'obb.index.sectors(symbol="^TX60", provider="tmx").to_df()',
|
||||
],
|
||||
)
|
||||
async def sectors(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
standard_params: StandardParams,
|
||||
extra_params: ExtraParams,
|
||||
) -> OBBject:
|
||||
"""Index Sectors. Sector weighting of an index."""
|
||||
return await OBBject.from_query(Query(**locals()))
|
||||
|
||||
@@ -175,6 +175,14 @@ def test_news_world(params, headers):
|
||||
"end_date": None,
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"symbol": "RBC",
|
||||
"limit": 20,
|
||||
"page": 1,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
|
||||
@@ -160,6 +160,14 @@ def test_news_world(params, obb):
|
||||
"end_date": None,
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "tmx",
|
||||
"symbol": "RBC",
|
||||
"limit": 20,
|
||||
"page": 1,
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.integration
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
"index_market": "/index/market",
|
||||
"index_price": "/index/price",
|
||||
"index_price_historical": "/index/price/historical",
|
||||
"index_sectors": "/index/sectors",
|
||||
"news": "/news",
|
||||
"news_company": "/news/company",
|
||||
"news_world": "/news/world",
|
||||
|
||||
888
openbb_platform/poetry.lock
generated
vendored
888
openbb_platform/poetry.lock
generated
vendored
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ class CboeIndexConstituentsQueryParams(IndexConstituentsQueryParams):
|
||||
Gets the current price data for all constituents of the CBOE European Index.
|
||||
"""
|
||||
|
||||
index: CONSTITUENTS_EU = Field(default="BUK100P")
|
||||
symbol: CONSTITUENTS_EU = Field(default="BUK100P")
|
||||
|
||||
|
||||
class CboeIndexConstituentsData(IndexConstituentsData):
|
||||
@@ -109,7 +109,7 @@ class CboeIndexConstituentsFetcher(
|
||||
|
||||
url = (
|
||||
"https://cdn.cboe.com/api/global/european_indices"
|
||||
f"/constituent_quotes/{query.index}.json"
|
||||
f"/constituent_quotes/{query.symbol}.json"
|
||||
)
|
||||
data = await amake_request(url)
|
||||
return data.get("data")
|
||||
|
||||
@@ -53,7 +53,7 @@ def test_cboe_index_historical_fetcher(credentials=test_credentials):
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_cboe_index_constituents_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "BUKBUS"}
|
||||
params = {"symbol": "BUK100P"}
|
||||
|
||||
fetcher = CboeIndexConstituentsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
|
||||
@@ -24,7 +24,7 @@ class FMPIndexConstituentsQueryParams(IndexConstituentsQueryParams):
|
||||
https://site.financialmodelingprep.com/developer/docs/list-of-nasdaq-companies-api/
|
||||
"""
|
||||
|
||||
index: Literal["dowjones", "sp500", "nasdaq"] = Field(
|
||||
symbol: Literal["dowjones", "sp500", "nasdaq"] = Field(
|
||||
default="dowjones",
|
||||
)
|
||||
|
||||
@@ -104,7 +104,7 @@ class FMPIndexConstituentsFetcher(
|
||||
api_key = credentials.get("fmp_api_key") if credentials else ""
|
||||
|
||||
base_url = "https://financialmodelingprep.com/api/v3"
|
||||
url = f"{base_url}/{query.index}_constituent/?apikey={api_key}"
|
||||
url = f"{base_url}/{query.symbol}_constituent/?apikey={api_key}"
|
||||
|
||||
return await get_data_many(url, **kwargs)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""US Government Treasury Auctions Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pandas as pd
|
||||
@@ -69,7 +70,9 @@ class GovernmentUSTreasuryAuctionsFetcher(
|
||||
if r.status_code != 200:
|
||||
raise RuntimeError(r.status_code)
|
||||
data = pd.DataFrame(r.json())
|
||||
results = data.replace("", None).convert_dtypes().to_dict("records")
|
||||
results = (
|
||||
data.fillna("N/A").replace("", None).replace("N/A", None).to_dict("records")
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""US Government Treasury Prices"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from io import StringIO
|
||||
@@ -8,15 +9,15 @@ from typing import Any, Dict, List, Literal, Optional
|
||||
import requests
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.treasury_prices import (
|
||||
USTreasuryPricesData,
|
||||
USTreasuryPricesQueryParams,
|
||||
TreasuryPricesData,
|
||||
TreasuryPricesQueryParams,
|
||||
)
|
||||
from openbb_government_us.utils.helpers import get_random_agent
|
||||
from pandas import read_csv, to_datetime
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class GovernmentUSTreasuryPricesQueryParams(USTreasuryPricesQueryParams):
|
||||
class GovernmentUSTreasuryPricesQueryParams(TreasuryPricesQueryParams):
|
||||
"""US Government Treasury Prices Query."""
|
||||
|
||||
cusip: Optional[str] = Field(description="Filter by CUSIP.", default=None)
|
||||
@@ -26,7 +27,7 @@ class GovernmentUSTreasuryPricesQueryParams(USTreasuryPricesQueryParams):
|
||||
)
|
||||
|
||||
|
||||
class GovernmentUSTreasuryPricesData(USTreasuryPricesData):
|
||||
class GovernmentUSTreasuryPricesData(TreasuryPricesData):
|
||||
"""US Government Treasury Prices Data."""
|
||||
|
||||
|
||||
@@ -135,7 +136,7 @@ class GovernmentUSTreasuryPricesFetcher(
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError("No data was returned: " + str(e)) from e
|
||||
raise RuntimeError(e) from e
|
||||
|
||||
if query.security_type is not None:
|
||||
results = results[
|
||||
|
||||
52
openbb_platform/providers/tmx/README.md
vendored
Normal file
52
openbb_platform/providers/tmx/README.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# openbb-tmx: A Community Data Provider Extension
|
||||
|
||||
`openbb-tmx` is an unofficial, community, data provider extension for the OpenBB Platform.
|
||||
|
||||
Install with `pip install openbb-tmx`, or from the local directory, `pip install -e .`
|
||||
|
||||
## Command Coverage
|
||||
|
||||
- .derivatives.options.chains
|
||||
- Historical EOD chains data available from 2009.
|
||||
- .equity.calendar.earnings
|
||||
- .equity.estimates.consensus
|
||||
- .equity.discovery.gainers
|
||||
- Includes a 'category' parameter for the type of 'best performer'.
|
||||
- .equity.fundamental.dividends
|
||||
- .equity.fundamental.filings
|
||||
- .equity.ownership.insider_trading
|
||||
- Does not use the Standard Model because the data returned are total shares traded over the previous 3,6,and 12 months.
|
||||
- .equity.price.quote
|
||||
- .equity.price.historical
|
||||
- Daily, weekly, monthly, and intraday - with valid intervals being any X number of minutes.
|
||||
- Weekly and monthly intervals are for the period beginning.
|
||||
- Historical intraday data begins April 14, 2022.
|
||||
- Split-adjusted, split and dividend adjusted, as well as unadjusted prices are available only for daily intervals. Other intervals are split-adjusted.
|
||||
- .equity.search
|
||||
- .equity.profile
|
||||
- .etf.search
|
||||
- .etf.info
|
||||
- .etf.sectors
|
||||
- .etf.countries
|
||||
- .etf.holdings
|
||||
- Top 10 holdings only.
|
||||
- .fixedincome.corporate.bond_prices
|
||||
- .fixedincome.government.treasury_prices
|
||||
- .index.constituents
|
||||
- Full constituents with weights and notional values.
|
||||
- .index.snapshots
|
||||
- Regions of: ["ca", "us"]
|
||||
- .index.available
|
||||
- Includes URLs to methedology and factsheet documents.
|
||||
- .index.sectors
|
||||
- .news.company
|
||||
|
||||
## Symbology
|
||||
|
||||
No exchange suffix is required to handle Canadian listings. The extension accepts `.TO` and `.TSX` as well as no suffix. Additionally, a composite ticker symbol can be entered. For example, `AAPL` trades as a Canadian Depositary Receipt, under the symbol `AAPL:AQN`, on the NEO Exchange. The US listing is also found as `AAPL:US`. Some US and select European data is available from some functions, like `quote` and `historical`.
|
||||
|
||||
Indices all begin with `^`.
|
||||
|
||||
## Caching
|
||||
|
||||
This extension uses `aiohttp-client-cache` with a SQL backend to cache symbol directories, ETF, and index data. It can be bypassed with the parameter, `use_cache=False`. ETF and index data is gathered from a single JSON file which is updated daily by the exchange. The HTTP request is cached for one day.
|
||||
0
openbb_platform/providers/tmx/__init__.py
Normal file
0
openbb_platform/providers/tmx/__init__.py
Normal file
69
openbb_platform/providers/tmx/openbb_tmx/__init__.py
Normal file
69
openbb_platform/providers/tmx/openbb_tmx/__init__.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""TMX Provider Module."""
|
||||
|
||||
from openbb_core.provider.abstract.provider import Provider
|
||||
from openbb_tmx.models.available_indices import TmxAvailableIndicesFetcher
|
||||
from openbb_tmx.models.bond_prices import TmxBondPricesFetcher
|
||||
from openbb_tmx.models.calendar_earnings import TmxCalendarEarningsFetcher
|
||||
from openbb_tmx.models.company_filings import TmxCompanyFilingsFetcher
|
||||
from openbb_tmx.models.company_news import TmxCompanyNewsFetcher
|
||||
from openbb_tmx.models.equity_historical import TmxEquityHistoricalFetcher
|
||||
from openbb_tmx.models.equity_profile import TmxEquityProfileFetcher
|
||||
from openbb_tmx.models.equity_quote import TmxEquityQuoteFetcher
|
||||
from openbb_tmx.models.equity_search import TmxEquitySearchFetcher
|
||||
from openbb_tmx.models.etf_countries import TmxEtfCountriesFetcher
|
||||
from openbb_tmx.models.etf_holdings import TmxEtfHoldingsFetcher
|
||||
from openbb_tmx.models.etf_info import TmxEtfInfoFetcher
|
||||
from openbb_tmx.models.etf_search import TmxEtfSearchFetcher
|
||||
from openbb_tmx.models.etf_sectors import TmxEtfSectorsFetcher
|
||||
from openbb_tmx.models.gainers import TmxGainersFetcher
|
||||
from openbb_tmx.models.historical_dividends import TmxHistoricalDividendsFetcher
|
||||
from openbb_tmx.models.index_constituents import TmxIndexConstituentsFetcher
|
||||
from openbb_tmx.models.index_sectors import TmxIndexSectorsFetcher
|
||||
from openbb_tmx.models.index_snapshots import TmxIndexSnapshotsFetcher
|
||||
from openbb_tmx.models.insider_trading import TmxInsiderTradingFetcher
|
||||
from openbb_tmx.models.options_chains import TmxOptionsChainsFetcher
|
||||
from openbb_tmx.models.price_target_consensus import TmxPriceTargetConsensusFetcher
|
||||
from openbb_tmx.models.treasury_prices import TmxTreasuryPricesFetcher
|
||||
|
||||
tmx_provider = Provider(
|
||||
name="tmx",
|
||||
website="https://www.tmx.com/",
|
||||
description="""Unofficial TMX Data Provider Extension
|
||||
TMX Group Companies
|
||||
- Toronto Stock Exchange
|
||||
- TSX Venture Exchange
|
||||
- TSX Trust
|
||||
- Montréal Exchange
|
||||
- TSX Alpha Exchange
|
||||
- Shorcan
|
||||
- CDCC
|
||||
- CDS
|
||||
- TMX Datalinx
|
||||
- Trayport
|
||||
""",
|
||||
fetcher_dict={
|
||||
"AvailableIndices": TmxAvailableIndicesFetcher,
|
||||
"BondPrices": TmxBondPricesFetcher,
|
||||
"CalendarEarnings": TmxCalendarEarningsFetcher,
|
||||
"CompanyFilings": TmxCompanyFilingsFetcher,
|
||||
"CompanyNews": TmxCompanyNewsFetcher,
|
||||
"EquityHistorical": TmxEquityHistoricalFetcher,
|
||||
"EquityInfo": TmxEquityProfileFetcher,
|
||||
"EquityQuote": TmxEquityQuoteFetcher,
|
||||
"EquitySearch": TmxEquitySearchFetcher,
|
||||
"EtfSearch": TmxEtfSearchFetcher,
|
||||
"EtfHoldings": TmxEtfHoldingsFetcher,
|
||||
"EtfSectors": TmxEtfSectorsFetcher,
|
||||
"EtfCountries": TmxEtfCountriesFetcher,
|
||||
"EtfInfo": TmxEtfInfoFetcher,
|
||||
"EquityGainers": TmxGainersFetcher,
|
||||
"HistoricalDividends": TmxHistoricalDividendsFetcher,
|
||||
"IndexConstituents": TmxIndexConstituentsFetcher,
|
||||
"IndexSectors": TmxIndexSectorsFetcher,
|
||||
"IndexSnapshots": TmxIndexSnapshotsFetcher,
|
||||
"InsiderTrading": TmxInsiderTradingFetcher,
|
||||
"OptionsChains": TmxOptionsChainsFetcher,
|
||||
"PriceTargetConsensus": TmxPriceTargetConsensusFetcher,
|
||||
"TreasuryPrices": TmxTreasuryPricesFetcher,
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,131 @@
|
||||
"""Available Indices fetcher for TMX"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.available_indices import (
|
||||
AvailableIndicesData,
|
||||
AvailableIndicesQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_tmx.utils.helpers import get_data_from_url, tmx_indices_backend
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class TmxAvailableIndicesQueryParams(AvailableIndicesQueryParams):
|
||||
"""TMX Available Indices Query Params."""
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request."
|
||||
+ " Index data is from a single JSON file, updated each day after close."
|
||||
+ " It is cached for one day. To bypass, set to False.",
|
||||
)
|
||||
|
||||
|
||||
class TmxAvailableIndicesData(AvailableIndicesData):
|
||||
"""TMX Available Indices Data."""
|
||||
|
||||
symbol: str = Field(description="The ticker symbol of the index.")
|
||||
|
||||
|
||||
class TmxAvailableIndicesFetcher(
|
||||
Fetcher[
|
||||
TmxAvailableIndicesQueryParams,
|
||||
List[TmxAvailableIndicesData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxAvailableIndicesQueryParams:
|
||||
"""Transform the query params."""
|
||||
return TmxAvailableIndicesQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxAvailableIndicesQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> Dict:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
url = "https://tmxinfoservices.com/files/indices/sptsx-indices.json"
|
||||
|
||||
data = await get_data_from_url(
|
||||
url,
|
||||
use_cache=query.use_cache,
|
||||
backend=tmx_indices_backend,
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxAvailableIndicesQueryParams,
|
||||
data: Dict,
|
||||
**kwargs: Any,
|
||||
) -> List[TmxAvailableIndicesData]:
|
||||
"""Transform the data to the standard format."""
|
||||
|
||||
data = data.copy()
|
||||
if data == {}:
|
||||
raise EmptyDataError
|
||||
|
||||
# Extract the category for each index.
|
||||
symbols = {}
|
||||
for category, symbol_list in data["groups"].items():
|
||||
for symbol in symbol_list:
|
||||
if symbol not in symbols:
|
||||
symbols[symbol] = category
|
||||
else:
|
||||
symbols[symbol].append(category)
|
||||
category = {"category": symbols} # noqa: PLW2901
|
||||
# Extract the data for each index and combine with the category.
|
||||
new_data = []
|
||||
for symbol in data["indices"]:
|
||||
overview = data["indices"][symbol].get("overview_en", None)
|
||||
if overview:
|
||||
# Remove HTML tags from the overview
|
||||
overview = re.sub("<.*?>", "", overview)
|
||||
# Remove additional artifacts from the overview
|
||||
overview = re.sub("\r|\n|amp;", "", overview)
|
||||
new_data.append(
|
||||
{
|
||||
"symbol": symbol,
|
||||
"name": data["indices"][symbol].get("name_en", None),
|
||||
"currency": (
|
||||
"USD"
|
||||
if "(USD)" in data["indices"][symbol]["name_en"]
|
||||
else "CAD"
|
||||
),
|
||||
"category": symbols[symbol],
|
||||
"market_value": (
|
||||
data["indices"][symbol]["quotedmarketvalue"].get("total", None)
|
||||
if data["indices"][symbol].get("quotedmarketvalue")
|
||||
else None
|
||||
),
|
||||
"num_constituents": data["indices"][symbol].get(
|
||||
"nb_constituents", None
|
||||
),
|
||||
"overview": (
|
||||
overview
|
||||
if data["indices"][symbol].get("overview") != ""
|
||||
else None
|
||||
),
|
||||
"methodology": (
|
||||
data["indices"][symbol].get("methodology", None)
|
||||
if data["indices"][symbol].get("methodology") != ""
|
||||
else None
|
||||
),
|
||||
"factsheet": (
|
||||
data["indices"][symbol].get("factsheet", None)
|
||||
if data["indices"][symbol].get("factsheet") != ""
|
||||
else None
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return [TmxAvailableIndicesData.model_validate(d) for d in new_data]
|
||||
189
openbb_platform/providers/tmx/openbb_tmx/models/bond_prices.py
Normal file
189
openbb_platform/providers/tmx/openbb_tmx/models/bond_prices.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""TMX Bond Prices Fetcher"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
timedelta,
|
||||
)
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.bond_reference import (
|
||||
BondReferenceData,
|
||||
BondReferenceQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import get_all_bonds
|
||||
from pandas import DataFrame
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxBondPricesQueryParams(BondReferenceQueryParams):
|
||||
"""
|
||||
TMX Bond Prices Query Params.
|
||||
|
||||
Data will be made available by 5:00 EST on T+1
|
||||
|
||||
Source: https://bondtradedata.iiroc.ca/#/
|
||||
"""
|
||||
|
||||
__alias_dict__ = {
|
||||
# "isin": "isins",
|
||||
}
|
||||
issue_date_min: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Filter by the minimum original issue date.",
|
||||
)
|
||||
issue_date_max: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Filter by the maximum original issue date.",
|
||||
)
|
||||
last_traded_min: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Filter by the minimum last trade date.",
|
||||
)
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="All bond data is sourced from a single JSON file that is updated daily."
|
||||
+ " The file is cached for one day to eliminate downloading more than once."
|
||||
+ " Caching will significantly speed up subsequent queries. To bypass, set to False.",
|
||||
)
|
||||
|
||||
|
||||
class TmxBondPricesData(BondReferenceData):
|
||||
"""TMX Bond Prices Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"coupon_rate": "couponRate",
|
||||
}
|
||||
|
||||
ytm: Optional[float] = Field(
|
||||
default=None,
|
||||
description="Yield to maturity (YTM) is the rate of return anticipated on a bond"
|
||||
+ " if it is held until the maturity date. It takes into account"
|
||||
+ " the current market price, par value, coupon rate and time to maturity. It is assumed that all"
|
||||
+ " coupons are reinvested at the same rate."
|
||||
+ " Values are returned as a normalized percent.",
|
||||
alias="lastYield",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
price: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The last price for the bond.",
|
||||
alias="lastPrice",
|
||||
)
|
||||
highest_price: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The highest price for the bond on the last traded date.",
|
||||
alias="highestPrice",
|
||||
)
|
||||
lowest_price: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The lowest price for the bond on the last traded date.",
|
||||
alias="lowestPrice",
|
||||
)
|
||||
total_trades: Optional[int] = Field(
|
||||
default=None,
|
||||
description="Total number of trades on the last traded date.",
|
||||
alias="totalTrades",
|
||||
)
|
||||
last_traded_date: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Last traded date of the bond.",
|
||||
alias="lastTradedDate",
|
||||
)
|
||||
maturity_date: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Maturity date of the bond.",
|
||||
alias="maturityDate",
|
||||
)
|
||||
issue_date: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Issue date of the bond. This is the date when the bond first accrues interest.",
|
||||
alias="originalIssueDate",
|
||||
)
|
||||
issuer_name: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Name of the issuing entity.",
|
||||
alias="issuer",
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"ytm",
|
||||
"coupon_rate",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return float(v) / 100 if v else None
|
||||
|
||||
|
||||
class TmxBondPricesFetcher(
|
||||
Fetcher[
|
||||
TmxBondPricesQueryParams,
|
||||
List[TmxBondPricesData],
|
||||
]
|
||||
):
|
||||
"""Tmx Bond Reference Fetcher."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxBondPricesQueryParams:
|
||||
"""Transform query params."""
|
||||
transformed_params = params.copy()
|
||||
now = datetime.now()
|
||||
if now.date().weekday() > 4:
|
||||
now = now - timedelta(now.date().weekday() - 4)
|
||||
if transformed_params.get("maturity_date_min") is None:
|
||||
transformed_params["maturity_date_min"] = (
|
||||
now - timedelta(days=1)
|
||||
).strftime("%Y-%m-%d")
|
||||
return TmxBondPricesQueryParams(**transformed_params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxBondPricesQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> DataFrame:
|
||||
"""Get the raw data containing all bond data."""
|
||||
bonds = await get_all_bonds(use_cache=query.use_cache)
|
||||
return bonds
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxBondPricesQueryParams,
|
||||
data: DataFrame,
|
||||
**kwargs: Any,
|
||||
) -> List[TmxBondPricesData]:
|
||||
"""Transform data."""
|
||||
bonds = data.copy()
|
||||
results = []
|
||||
data = data[data["bondType"] == "Corp"]
|
||||
|
||||
data = bonds.query(
|
||||
"bondType == 'Corp'"
|
||||
"& maturityDate >= @query.maturity_date_min.strftime('%Y-%m-%d')"
|
||||
).sort_values(by=["maturityDate"])
|
||||
data.issuer = data.loc[:, "issuer"].str.strip()
|
||||
if query.maturity_date_max:
|
||||
data = data.query(
|
||||
"maturityDate <= @query.maturity_date_max.strftime('%Y-%m-%d')"
|
||||
)
|
||||
if query.last_traded_min:
|
||||
data = data.query(
|
||||
"lastTradedDate >= @query.last_traded_min.strftime('%Y-%m-%d')"
|
||||
)
|
||||
if query.coupon_min:
|
||||
data = data.query("couponRate >= @query.coupon_min")
|
||||
if query.coupon_max:
|
||||
data = data.query("couponRate <= @query.coupon_max")
|
||||
if query.issuer_name:
|
||||
data = data.query("issuer.str.contains(@query.issuer_name, case=False)")
|
||||
if len(data) > 0:
|
||||
data = data.drop(columns=["bondType", "securityId", "secKey"])
|
||||
data = data.fillna("N/A").replace("N/A", None)
|
||||
results = data.to_dict("records")
|
||||
|
||||
return [TmxBondPricesData.model_validate(d) for d in results]
|
||||
@@ -0,0 +1,142 @@
|
||||
"""TMX Earnings Calendar Model"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.calendar_earnings import (
|
||||
CalendarEarningsData,
|
||||
CalendarEarningsQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pandas import date_range
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxCalendarEarningsQueryParams(CalendarEarningsQueryParams):
|
||||
"""TMX Calendar Earnings Query."""
|
||||
|
||||
|
||||
class TmxCalendarEarningsData(CalendarEarningsData):
|
||||
"""TMX Calendar Earnings Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"eps_actual": "actualEps",
|
||||
"reporting_time": "announceTime",
|
||||
"eps_consensus": "estimatedEps",
|
||||
"eps_surprise": "epsSurpriseDollar",
|
||||
"surprise_percent": "epsSurprisePercent",
|
||||
}
|
||||
|
||||
name: str = Field(description="The company's name.", alias="companyName")
|
||||
eps_consensus: Optional[float] = Field(
|
||||
default=None, description="The consensus estimated EPS in dollars."
|
||||
)
|
||||
eps_actual: Optional[float] = Field(
|
||||
default=None, description="The actual EPS in dollars."
|
||||
)
|
||||
eps_surprise: Optional[float] = Field(
|
||||
default=None, description="The EPS surprise in dollars."
|
||||
)
|
||||
surprise_percent: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The EPS surprise as a normalized percent.",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
reporting_time: Optional[str] = Field(
|
||||
default=None,
|
||||
description="The time of the report - i.e., before or after market.",
|
||||
)
|
||||
|
||||
@field_validator("surprise_percent", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def percent_validate(cls, v): # pylint: disable=E0213
|
||||
"""Return the percent as a normalized value."""
|
||||
return float(v) / 100 if v else None
|
||||
|
||||
|
||||
class TmxCalendarEarningsFetcher(
|
||||
Fetcher[TmxCalendarEarningsQueryParams, List[TmxCalendarEarningsData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxCalendarEarningsQueryParams:
|
||||
"""Transform the query."""
|
||||
transformed_params = params.copy()
|
||||
if transformed_params.get("start_date") is None:
|
||||
transformed_params["start_date"] = (
|
||||
datetime.now().date().strftime("%Y-%m-%d")
|
||||
)
|
||||
if transformed_params.get("end_date") is None:
|
||||
transformed_params["end_date"] = (
|
||||
(datetime.now() + timedelta(days=5)).date().strftime("%Y-%m-%d")
|
||||
)
|
||||
return TmxCalendarEarningsQueryParams(**transformed_params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxCalendarEarningsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
results = []
|
||||
dates = []
|
||||
user_agent = get_random_agent()
|
||||
dates = date_range(query.start_date, end=query.end_date)
|
||||
|
||||
async def create_task(date, results):
|
||||
"""Creates a task for a single date in the range."""
|
||||
data = []
|
||||
date = date.strftime("%Y-%m-%d")
|
||||
payload = gql.get_earnings_date_payload.copy()
|
||||
payload["variables"]["date"] = date
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
r = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"Host": "app-money.tmx.com",
|
||||
"Referer": "https://money.tmx.com/",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=3,
|
||||
)
|
||||
try:
|
||||
if (
|
||||
"data" in r
|
||||
and r["data"].get("getEnhancedEarningsForDate") is not None
|
||||
):
|
||||
data = r["data"].get("getEnhancedEarningsForDate")
|
||||
data = [{"report_date": date, **d} for d in data]
|
||||
except Exception as e:
|
||||
raise RuntimeError(e) from e
|
||||
if len(data) > 0:
|
||||
results.extend(data)
|
||||
return results
|
||||
|
||||
tasks = [create_task(date, results) for date in dates if date.weekday() < 5]
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
return sorted(results, key=lambda x: x["report_date"])
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxCalendarEarningsQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxCalendarEarningsData]:
|
||||
"""Return the transformed data."""
|
||||
results = [{k: (None if v == "N/A" else v) for k, v in d.items()} for d in data]
|
||||
return [TmxCalendarEarningsData.model_validate(d) for d in results]
|
||||
@@ -0,0 +1,169 @@
|
||||
"""TMX Company Filings Model"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
timedelta,
|
||||
)
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from dateutil import rrule
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.company_filings import (
|
||||
CompanyFilingsData,
|
||||
CompanyFilingsQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class TmxCompanyFilingsQueryParams(CompanyFilingsQueryParams):
|
||||
"""TMX Company Filings Query Parameters."""
|
||||
|
||||
start_date: Optional[dateType] = Field(
|
||||
description="The start date to fetch.",
|
||||
default=None,
|
||||
)
|
||||
end_date: Optional[dateType] = Field(
|
||||
description="The end date to fetch.",
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
class TmxCompanyFilingsData(CompanyFilingsData):
|
||||
"""TMX Sedar Filings Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"filing_date": "filingDate",
|
||||
"report_type": "name",
|
||||
"report_url": "urlToPdf",
|
||||
}
|
||||
|
||||
description: str = Field(description="The description of the filing.")
|
||||
size: str = Field(description="The file size of the PDF document.")
|
||||
|
||||
|
||||
class TmxCompanyFilingsFetcher(
|
||||
Fetcher[TmxCompanyFilingsQueryParams, List[TmxCompanyFilingsData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxCompanyFilingsQueryParams:
|
||||
"""Transform the query."""
|
||||
transformed_params = params.copy()
|
||||
if transformed_params.get("start_date") is None:
|
||||
transformed_params["start_date"] = (
|
||||
datetime.now() - timedelta(weeks=16)
|
||||
).strftime("%Y-%m-%d")
|
||||
if transformed_params.get("end_date") is None:
|
||||
transformed_params["end_date"] = datetime.now().date().strftime("%Y-%m-%d")
|
||||
transformed_params["symbol"] = (
|
||||
params.get("symbol", "")
|
||||
.upper()
|
||||
.replace("-", ".")
|
||||
.replace(".TO", "")
|
||||
.replace(".TSX", "")
|
||||
)
|
||||
return TmxCompanyFilingsQueryParams(**transformed_params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxCompanyFilingsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
user_agent = get_random_agent()
|
||||
results: List[Dict] = []
|
||||
|
||||
# Generate a list of dates from start_date to end_date with a frequency of 1 week
|
||||
dates = list(
|
||||
rrule.rrule(
|
||||
rrule.WEEKLY, interval=1, dtstart=query.start_date, until=query.end_date
|
||||
)
|
||||
)
|
||||
|
||||
# Add end_date to the list if it's not there already
|
||||
if dates[-1] != query.end_date:
|
||||
dates.append(query.end_date) # type: ignore
|
||||
|
||||
# Create a list of 4-week chunks
|
||||
chunks = [
|
||||
(dates[i], dates[i + 1] - timedelta(days=1)) for i in range(len(dates) - 1)
|
||||
]
|
||||
|
||||
# Adjust the end date of the last chunk to be the final end date
|
||||
chunks[-1] = (chunks[-1][0], query.end_date) # type: ignore
|
||||
|
||||
async def create_task(start, end, results):
|
||||
"""Create tasks from the chunked start/end dates."""
|
||||
data = []
|
||||
payload = gql.get_company_filings_payload
|
||||
payload["variables"]["symbol"] = query.symbol
|
||||
payload["variables"]["fromDate"] = start.strftime("%Y-%m-%d")
|
||||
payload["variables"]["toDate"] = end.strftime("%Y-%m-%d")
|
||||
payload["variables"]["limit"] = 1000
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
|
||||
async def try_again():
|
||||
return await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": f"https://money.tmx.com/en/quote/{query.symbol}",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
try:
|
||||
data = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": f"https://money.tmx.com/en/quote/{query.symbol}",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
except Exception:
|
||||
data = await try_again()
|
||||
|
||||
if isinstance(data, str):
|
||||
data = await try_again()
|
||||
|
||||
if data != [] and data["data"].get("filings") is not None:
|
||||
results.extend(data["data"]["filings"])
|
||||
|
||||
return results
|
||||
|
||||
tasks = [create_task(chunk[0], chunk[1], results) for chunk in chunks]
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
return sorted(results, key=lambda x: x["filingDate"], reverse=True)
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxCompanyFilingsQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxCompanyFilingsData]:
|
||||
"""Return the transformed data."""
|
||||
return [TmxCompanyFilingsData.model_validate(d) for d in data]
|
||||
123
openbb_platform/providers/tmx/openbb_tmx/models/company_news.py
Normal file
123
openbb_platform/providers/tmx/openbb_tmx/models/company_news.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""TMX Stock News model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pytz
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.company_news import (
|
||||
CompanyNewsData,
|
||||
CompanyNewsQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxCompanyNewsQueryParams(CompanyNewsQueryParams):
|
||||
"""TMX Stock News query."""
|
||||
|
||||
__json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
|
||||
|
||||
page: Optional[int] = Field(
|
||||
default=1, description="The page number to start from. Use with limit."
|
||||
)
|
||||
|
||||
|
||||
class TmxCompanyNewsData(CompanyNewsData):
|
||||
"""TMX Stock News Data"""
|
||||
|
||||
__alias_dict__ = {
|
||||
"date": "datetime",
|
||||
"title": "headline",
|
||||
}
|
||||
|
||||
source: Optional[str] = Field(description="Source of the news.", default=None)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def date_validate(cls, v): # pylint: disable=E0213
|
||||
"""Validate the datetime format."""
|
||||
dt = datetime.strptime(v, "%Y-%m-%dT%H:%M:%S%z")
|
||||
return dt.astimezone(pytz.timezone("America/New_York"))
|
||||
|
||||
|
||||
class TmxCompanyNewsFetcher(
|
||||
Fetcher[TmxCompanyNewsQueryParams, List[TmxCompanyNewsData]],
|
||||
):
|
||||
"""TMX Stock News Fetcher."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxCompanyNewsQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxCompanyNewsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxCompanyNewsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
user_agent = get_random_agent()
|
||||
symbols = query.symbol.split(",")
|
||||
results = []
|
||||
|
||||
async def create_task(symbol, results):
|
||||
"""Makes a POST request to the TMX GraphQL endpoint for a single symbol."""
|
||||
|
||||
symbol = (
|
||||
symbol.upper().replace(".TO", "").replace(".TSX", "").replace("-", ".")
|
||||
)
|
||||
payload = gql.get_company_news_events_payload
|
||||
payload["variables"]["symbol"] = symbol
|
||||
payload["variables"]["page"] = query.page
|
||||
payload["variables"]["limit"] = query.limit
|
||||
payload["variables"]["locale"] = "en"
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
data = {}
|
||||
response = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": f"https://money.tmx.com/en/quote/{symbol}",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=3,
|
||||
)
|
||||
data = response["data"] if response.get("data") else data
|
||||
if data.get("news") is not None:
|
||||
news = data["news"]
|
||||
for i in range(len(news)): # pylint: disable=C0200
|
||||
url = f"https://money.tmx.com/quote/{symbol.upper()}/news/{news[i]['newsid']}"
|
||||
news[i]["url"] = url
|
||||
# The newsid was used to create the URL, so we drop it.
|
||||
news[i].pop("newsid", None)
|
||||
# The summary is a duplicated headline, so we drop it.
|
||||
news[i].pop("summary", None)
|
||||
# Add the symbol to the data for multi-ticker support.
|
||||
news[i]["symbols"] = symbol
|
||||
results.extend(news)
|
||||
|
||||
return results
|
||||
|
||||
tasks = [create_task(symbol, results) for symbol in symbols]
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
return sorted(results, key=lambda d: d["datetime"], reverse=True)
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxCompanyNewsQueryParams, data: List[Dict], **kwargs: Any
|
||||
) -> List[TmxCompanyNewsData]:
|
||||
"""Return the transformed data."""
|
||||
return [TmxCompanyNewsData.model_validate(d) for d in data]
|
||||
@@ -0,0 +1,239 @@
|
||||
"""TMX Equity Historical Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import asyncio
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
|
||||
import pytz
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.equity_historical import (
|
||||
EquityHistoricalData,
|
||||
EquityHistoricalQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import (
|
||||
QUERY_DESCRIPTIONS,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_tmx.utils.helpers import (
|
||||
get_daily_price_history,
|
||||
get_intraday_price_history,
|
||||
get_weekly_or_monthly_price_history,
|
||||
)
|
||||
from pandas import DataFrame, to_datetime
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
_warn = warnings.warn
|
||||
|
||||
|
||||
class TmxEquityHistoricalQueryParams(EquityHistoricalQueryParams):
|
||||
"""
|
||||
TMX Equity Historical Query Params.
|
||||
|
||||
Ticker symbols are assumed to be Canadian listings when no suffix is provided.
|
||||
".TO" or ."TSX" are accepted but will automatically be removed.
|
||||
|
||||
US tickers are supported via their composite format: "AAPL:US"
|
||||
|
||||
Canadian Depositary Receipts (CDRs) are: "AAPL:AQL"
|
||||
|
||||
CDRs are the underlying asset for CAD-hedged assets.
|
||||
|
||||
source: https://money.tmx.com
|
||||
"""
|
||||
|
||||
__json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
|
||||
|
||||
interval: Union[
|
||||
Literal["1m", "2m", "5m", "15m", "30m", "60m", "1h", "1d", "1W", "1M"], str, int
|
||||
] = Field(
|
||||
description=QUERY_DESCRIPTIONS.get("interval", "")
|
||||
+ " Or, any integer (entered as a string) representing the number of minutes."
|
||||
+ " Default is daily data."
|
||||
+ " There is no extended hours data, and intraday data is limited to after April 12 2022.",
|
||||
default="day",
|
||||
)
|
||||
adjustment: Literal["splits_only", "splits_and_dividends", "unadjusted"] = Field(
|
||||
description="The adjustment factor to apply. Only valid for daily data.",
|
||||
default="splits_only",
|
||||
)
|
||||
|
||||
@field_validator("interval", mode="after", check_fields=False)
|
||||
@classmethod
|
||||
def validate_interval(cls, v): # pylint: disable=R0911
|
||||
"""Validate the interval to be valid for the TMX request."""
|
||||
if v is None or v == "day":
|
||||
return "day"
|
||||
if v in ("1M", "1mo", "month"):
|
||||
return "month"
|
||||
if "m" in v:
|
||||
return int(v.replace("m", ""))
|
||||
if "h" in v:
|
||||
return int(v.replace("h", "")) * 60
|
||||
if v == "1d":
|
||||
return "day"
|
||||
if v in ("1W", "1w", "week"):
|
||||
return "week"
|
||||
if v.isnumeric():
|
||||
return int(v)
|
||||
raise ValueError(f"Invalid interval: {v}")
|
||||
|
||||
|
||||
class TmxEquityHistoricalData(EquityHistoricalData):
|
||||
"""TMX Equity Historical Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"date": "datetime",
|
||||
"open": "openPrice",
|
||||
"close": "closePrice",
|
||||
"transactions_value": "tradeValue",
|
||||
"transactions": "numberOfTrade",
|
||||
"change_percent": "changePercent",
|
||||
}
|
||||
|
||||
vwap: Optional[float] = Field(
|
||||
description="Volume weighted average price for the day.", default=None
|
||||
)
|
||||
change: Optional[float] = Field(description="Change in price.", default=None)
|
||||
change_percent: Optional[float] = Field(
|
||||
description="Change in price, as a normalized percentage.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
transactions: Optional[int] = Field(
|
||||
description="Total number of transactions recorded.", default=None
|
||||
)
|
||||
transactions_value: Optional[float] = Field(
|
||||
description="Nominal value of recorded transactions.", default=None
|
||||
)
|
||||
|
||||
@field_validator("date", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def date_validate(cls, v): # pylint: disable=W0221
|
||||
"""Validate the datetime format."""
|
||||
try:
|
||||
dt = datetime.strptime(v, "%Y-%m-%d %H:%M:%S%z")
|
||||
return dt.astimezone(pytz.timezone("America/New_York"))
|
||||
except ValueError:
|
||||
return datetime.strptime(v, "%Y-%m-%d")
|
||||
|
||||
|
||||
class TmxEquityHistoricalFetcher(
|
||||
Fetcher[TmxEquityHistoricalQueryParams, List[TmxEquityHistoricalData]]
|
||||
):
|
||||
"""TMX Equity Historical Fetcher."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEquityHistoricalQueryParams:
|
||||
"""Transform the query."""
|
||||
adjustment = params.get("adjustment")
|
||||
if (
|
||||
adjustment is not None
|
||||
and adjustment != "splits_only"
|
||||
and params.get("interval") not in ["day", "1d"]
|
||||
):
|
||||
_warn("Adjustment parameter is only available for daily data.")
|
||||
return TmxEquityHistoricalQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEquityHistoricalQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
results: List[Dict] = []
|
||||
symbols = query.symbol.split(",")
|
||||
|
||||
async def create_task(symbol, results):
|
||||
"""Makes a POST request to the TMX GraphQL endpoint for a single ticker."""
|
||||
data: List[Dict] = []
|
||||
# A different request is used for each type of interval.
|
||||
if query.interval == "day":
|
||||
data = await get_daily_price_history(
|
||||
symbol,
|
||||
start_date=query.start_date,
|
||||
end_date=query.end_date,
|
||||
adjustment=query.adjustment,
|
||||
)
|
||||
if query.interval in ("week", "month"):
|
||||
data = await get_weekly_or_monthly_price_history(
|
||||
symbol,
|
||||
start_date=query.start_date,
|
||||
end_date=query.end_date,
|
||||
interval=query.interval, # type: ignore
|
||||
)
|
||||
if isinstance(query.interval, int):
|
||||
data = await get_intraday_price_history(
|
||||
symbol,
|
||||
interval=query.interval,
|
||||
start_date=query.start_date,
|
||||
end_date=query.end_date,
|
||||
)
|
||||
|
||||
if data != []:
|
||||
# Add the symbol to the data for multi-ticker support.
|
||||
data = [{**d, "symbol": symbol} for d in data]
|
||||
results.extend(data)
|
||||
|
||||
if data == []:
|
||||
_warn(f"No data found for {symbol}.")
|
||||
|
||||
return results
|
||||
|
||||
tasks = [create_task(symbol, results) for symbol in symbols]
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEquityHistoricalQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxEquityHistoricalData]:
|
||||
"""Return the transformed data."""
|
||||
|
||||
results = DataFrame(data)
|
||||
if results.empty or len(results) == 0:
|
||||
raise EmptyDataError()
|
||||
|
||||
# Handle the date formatting differences.
|
||||
results = results.rename(columns={"dateTime": "datetime"})
|
||||
if query.interval != "day":
|
||||
results["datetime"] = to_datetime(results["datetime"], utc=True)
|
||||
if query.interval in ("week", "month"):
|
||||
results["datetime"] = results["datetime"].dt.strftime("%Y-%m-%d")
|
||||
else:
|
||||
results["datetime"] = results["datetime"].dt.strftime(
|
||||
"%Y-%m-%d %H:%M:%S%z"
|
||||
)
|
||||
if query.interval == "day":
|
||||
results["datetime"] = to_datetime(results["datetime"]).dt.strftime(
|
||||
"%Y-%m-%d"
|
||||
)
|
||||
|
||||
symbols = query.symbol.split(",")
|
||||
# If there are multiple symbols, sort the data by datetime and symbol.
|
||||
if len(symbols) > 1:
|
||||
results = results.set_index(["datetime", "symbol"]).sort_index()
|
||||
results = results.reset_index()
|
||||
# If there is only one symbol, drop the symbol column.
|
||||
if len(symbols) == 1:
|
||||
results = results.drop(columns=["symbol"])
|
||||
# Normalizes the percent change values.
|
||||
if "changePercent" in results.columns:
|
||||
results["changePercent"] = results["changePercent"].astype(float) / 100
|
||||
# For the week beginning 2011-09-12 replace the openPrice NaN with 0 because of 9/11.
|
||||
if query.interval == "week":
|
||||
results["open"] = results["open"].fillna(0)
|
||||
# Convert any NaN values to None.
|
||||
results = results.fillna(value="N/A").replace("N/A", None)
|
||||
|
||||
return [
|
||||
TmxEquityHistoricalData.model_validate(d)
|
||||
for d in results.to_dict("records")
|
||||
]
|
||||
@@ -0,0 +1,164 @@
|
||||
"""TMX Equity Profile fetcher"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.equity_info import (
|
||||
EquityInfoData,
|
||||
EquityInfoQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pydantic import Field, model_validator
|
||||
|
||||
|
||||
class TmxEquityProfileQueryParams(EquityInfoQueryParams):
|
||||
"""TMX Equity Profile query params."""
|
||||
|
||||
__json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
|
||||
|
||||
|
||||
class TmxEquityProfileData(EquityInfoData):
|
||||
"""TMX Equity Profile Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"short_description": "shortDescription",
|
||||
"long_description": "longDescription",
|
||||
"company_url": "website",
|
||||
"business_phone_no": "phoneNumber",
|
||||
"business_address": "fullAddress",
|
||||
"stock_exchange": "exchangeCode",
|
||||
"industry_category": "industry",
|
||||
"industry_group": "qmdescription",
|
||||
}
|
||||
email: Optional[str] = Field(description="The email of the company.", default=None)
|
||||
issue_type: Optional[str] = Field(
|
||||
description="The issuance type of the asset.", default=None, alias="issueType"
|
||||
)
|
||||
shares_outstanding: Optional[int] = Field(
|
||||
description="The number of listed shares outstanding.",
|
||||
default=None,
|
||||
alias="shareOutStanding",
|
||||
)
|
||||
shares_escrow: Optional[int] = Field(
|
||||
description="The number of shares held in escrow.",
|
||||
default=None,
|
||||
alias="sharesESCROW",
|
||||
)
|
||||
shares_total: Optional[int] = Field(
|
||||
description="The total number of shares outstanding from all classes.",
|
||||
default=None,
|
||||
alias="totalSharesOutStanding",
|
||||
)
|
||||
dividend_frequency: Optional[str] = Field(
|
||||
description="The dividend frequency.", default=None
|
||||
)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def validate_empty_strings(cls, values) -> Dict:
|
||||
"""Validate the query parameters."""
|
||||
return {k: None if v == "" else v for k, v in values.items()}
|
||||
|
||||
|
||||
class TmxEquityProfileFetcher(
|
||||
Fetcher[
|
||||
TmxEquityProfileQueryParams,
|
||||
List[TmxEquityProfileData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEquityProfileQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxEquityProfileQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEquityProfileQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
symbols = query.symbol.split(",")
|
||||
|
||||
# The list where the results will be stored and appended to.
|
||||
results = []
|
||||
user_agent = get_random_agent()
|
||||
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
|
||||
async def create_task(symbol: str, results) -> None:
|
||||
"""Makes a POST request to the TMX GraphQL endpoint for a single symbol."""
|
||||
|
||||
symbol = (
|
||||
symbol.upper().replace("-", ".").replace(".TO", "").replace(".TSX", "")
|
||||
)
|
||||
|
||||
payload = gql.stock_info_payload.copy()
|
||||
payload["variables"]["symbol"] = symbol
|
||||
|
||||
data = {}
|
||||
r = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": f"https://money.tmx.com/en/quote/{symbol}",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=3,
|
||||
)
|
||||
if r["data"].get("getQuoteBySymbol"):
|
||||
data = r["data"]["getQuoteBySymbol"]
|
||||
results.append(data)
|
||||
|
||||
tasks = [create_task(symbol, results) for symbol in symbols]
|
||||
await asyncio.gather(*tasks)
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEquityProfileQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxEquityProfileData]:
|
||||
"""Return the transformed data."""
|
||||
|
||||
# Get only the items associated with `equity.profile()`.
|
||||
items_list = [
|
||||
"shortDescription",
|
||||
"longDescription",
|
||||
"website",
|
||||
"phoneNumber",
|
||||
"fullAddress",
|
||||
"sector",
|
||||
"qmdescription",
|
||||
"industry",
|
||||
"exchangeCode",
|
||||
"shareOutStanding",
|
||||
"sharesESCROW",
|
||||
"totalSharesOutStanding",
|
||||
"email",
|
||||
"issueType",
|
||||
"name",
|
||||
"symbol",
|
||||
"dividendFrequency",
|
||||
"employees",
|
||||
]
|
||||
data = [{k: v for k, v in d.items() if k in items_list} for d in data]
|
||||
# Sort the data by the order of the symbols in the query.
|
||||
symbols = query.symbol.split(",")
|
||||
symbol_to_index = {symbol: index for index, symbol in enumerate(symbols)}
|
||||
data = sorted(data, key=lambda d: symbol_to_index[d["symbol"]])
|
||||
|
||||
return [TmxEquityProfileData.model_validate(d) for d in data]
|
||||
360
openbb_platform/providers/tmx/openbb_tmx/models/equity_quote.py
Normal file
360
openbb_platform/providers/tmx/openbb_tmx/models/equity_quote.py
Normal file
@@ -0,0 +1,360 @@
|
||||
"""TMX Equity Profile fetcher"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import warnings
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
)
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from numpy import nan
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.equity_quote import (
|
||||
EquityQuoteData,
|
||||
EquityQuoteQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
_warn = warnings.warn
|
||||
|
||||
|
||||
class TmxEquityQuoteQueryParams(EquityQuoteQueryParams):
|
||||
"""TMX Equity Profile query params."""
|
||||
|
||||
__json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
|
||||
|
||||
|
||||
class TmxEquityQuoteData(EquityQuoteData):
|
||||
"""TMX Equity Profile Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"last_price": "price",
|
||||
"open": "openPrice",
|
||||
"high": "dayHigh",
|
||||
"low": "dayLow",
|
||||
"change": "priceChange",
|
||||
"change_percent": "percentChange",
|
||||
"prev_close": "prevClose",
|
||||
"stock_exchange": "exchangeCode",
|
||||
"industry_category": "industry",
|
||||
"industry_group": "qmdescription",
|
||||
"exchange": "exchangeCode",
|
||||
"security_type": "datatype",
|
||||
}
|
||||
name: Optional[str] = Field(default=None, description="The name of the asset.")
|
||||
security_type: Optional[str] = Field(
|
||||
description="The issuance type of the asset.", default=None
|
||||
)
|
||||
exchange: Optional[str] = Field(
|
||||
default=None,
|
||||
description="The listing exchange code.",
|
||||
)
|
||||
sector: Optional[str] = Field(default=None, description="The sector of the asset.")
|
||||
industry_category: Optional[str] = Field(
|
||||
default=None,
|
||||
description="The industry category of the asset.",
|
||||
)
|
||||
industry_group: Optional[str] = Field(
|
||||
default=None,
|
||||
description="The industry group of the asset.",
|
||||
)
|
||||
last_price: Optional[float] = Field(
|
||||
default=None, description="The last price of the asset."
|
||||
)
|
||||
open: Optional[float] = Field(
|
||||
default=None,
|
||||
description=DATA_DESCRIPTIONS.get("open", ""),
|
||||
)
|
||||
high: Optional[float] = Field(
|
||||
default=None,
|
||||
description=DATA_DESCRIPTIONS.get("high", ""),
|
||||
)
|
||||
low: Optional[float] = Field(
|
||||
default=None,
|
||||
description=DATA_DESCRIPTIONS.get("low", ""),
|
||||
)
|
||||
close: Optional[float] = Field(
|
||||
default=None,
|
||||
)
|
||||
vwap: Optional[float] = Field(
|
||||
default=None, description=DATA_DESCRIPTIONS.get("vwap", "")
|
||||
)
|
||||
volume: Optional[int] = Field(
|
||||
default=None, description=DATA_DESCRIPTIONS.get("vwap", "")
|
||||
)
|
||||
prev_close: Optional[float] = Field(
|
||||
default=None, description=DATA_DESCRIPTIONS.get("prev_close", "")
|
||||
)
|
||||
change: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The change in price.",
|
||||
)
|
||||
change_percent: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The change in price as a normalized percent.",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
year_high: Optional[float] = Field(
|
||||
description="Fifty-two week high.", default=None, alias="weeks52high"
|
||||
)
|
||||
year_low: Optional[float] = Field(
|
||||
description="Fifty-two week low.", default=None, alias="weeks52low"
|
||||
)
|
||||
ma_21: Optional[float] = Field(
|
||||
description="Twenty-one day moving average.",
|
||||
default=None,
|
||||
alias="day21MovingAvg",
|
||||
)
|
||||
ma_50: Optional[float] = Field(
|
||||
description="Fifty day moving average.", default=None, alias="day50MovingAvg"
|
||||
)
|
||||
ma_200: Optional[float] = Field(
|
||||
description="Two-hundred day moving average.",
|
||||
default=None,
|
||||
alias="day200MovingAvg",
|
||||
)
|
||||
volume_avg_10d: Optional[int] = Field(
|
||||
description="Ten day average volume.", default=None, alias="averageVolume10D"
|
||||
)
|
||||
volume_avg_30d: Optional[int] = Field(
|
||||
description="Thirty day average volume.", default=None, alias="averageVolume30D"
|
||||
)
|
||||
volume_avg_50d: Optional[int] = Field(
|
||||
description="Fifty day average volume.", default=None, alias="averageVolume50D"
|
||||
)
|
||||
market_cap: Optional[int] = Field(
|
||||
description="Market capitalization.", default=None, alias="MarketCap"
|
||||
)
|
||||
market_cap_all_classes: Optional[int] = Field(
|
||||
description="Market capitalization of all share classes.",
|
||||
default=None,
|
||||
alias="MarketCapAllClasses",
|
||||
)
|
||||
div_amount: Optional[float] = Field(
|
||||
description="The most recent dividend amount.",
|
||||
default=None,
|
||||
alias="dividendAmount",
|
||||
)
|
||||
div_currency: Optional[str] = Field(
|
||||
description="The currency the dividend is paid in.",
|
||||
default=None,
|
||||
alias="dividendCurrency",
|
||||
)
|
||||
div_yield: Optional[float] = Field(
|
||||
description="The dividend yield as a normalized percentage.",
|
||||
default=None,
|
||||
alias="dividendYield",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
div_freq: Optional[str] = Field(
|
||||
description="The frequency of dividend payments.",
|
||||
default=None,
|
||||
alias="dividendFrequency",
|
||||
)
|
||||
div_ex_date: Optional[dateType] = Field(
|
||||
description="The ex-dividend date.", default=None, alias="exDividendDate"
|
||||
)
|
||||
div_pay_date: Optional[dateType] = Field(
|
||||
description="The next dividend ayment date.",
|
||||
default=None,
|
||||
alias="dividendPayDate",
|
||||
)
|
||||
div_growth_3y: Optional[Union[float, str]] = Field(
|
||||
description="The three year dividend growth as a normalized percentage.",
|
||||
default=None,
|
||||
alias="dividend3Years",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
div_growth_5y: Optional[Union[float, str]] = Field(
|
||||
description="The five year dividend growth as a normalized percentage.",
|
||||
default=None,
|
||||
alias="dividend5Years",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
pe: Optional[Union[float, str]] = Field(
|
||||
description="The price to earnings ratio.", default=None, alias="peRatio"
|
||||
)
|
||||
eps: Optional[Union[float, str]] = Field(
|
||||
description="The earnings per share.", default=None
|
||||
)
|
||||
debt_to_equity: Optional[Union[float, str]] = Field(
|
||||
description="The debt to equity ratio.", default=None, alias="totalDebtToEquity"
|
||||
)
|
||||
price_to_book: Optional[Union[float, str]] = Field(
|
||||
description="The price to book ratio.", default=None, alias="priceToBook"
|
||||
)
|
||||
price_to_cf: Optional[Union[float, str]] = Field(
|
||||
description="The price to cash flow ratio.",
|
||||
default=None,
|
||||
alias="priceToCashFlow",
|
||||
)
|
||||
return_on_equity: Optional[Union[float, str]] = Field(
|
||||
description="The return on equity, as a normalized percentage.",
|
||||
default=None,
|
||||
alias="returnOnEquity",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_on_assets: Optional[Union[float, str]] = Field(
|
||||
description="The return on assets, as a normalized percentage.",
|
||||
default=None,
|
||||
alias="returnOnAssets",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
beta: Optional[Union[float, str]] = Field(
|
||||
description="The beta relative to the TSX Composite.", default=None
|
||||
)
|
||||
alpha: Optional[Union[float, str]] = Field(
|
||||
description="The alpha relative to the TSX Composite.", default=None
|
||||
)
|
||||
shares_outstanding: Optional[int] = Field(
|
||||
description="The number of listed shares outstanding.",
|
||||
default=None,
|
||||
alias="shareOutStanding",
|
||||
)
|
||||
shares_escrow: Optional[int] = Field(
|
||||
description="The number of shares held in escrow.",
|
||||
default=None,
|
||||
alias="sharesESCROW",
|
||||
)
|
||||
shares_total: Optional[int] = Field(
|
||||
description="The total number of shares outstanding from all classes.",
|
||||
default=None,
|
||||
alias="totalSharesOutStanding",
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"div_ex_date",
|
||||
"div_pay_date",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def date_validate(cls, v): # pylint: disable=E0213
|
||||
"""Return the datetime object from the date string"""
|
||||
if v:
|
||||
try:
|
||||
return datetime.strptime(v, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
return datetime.strptime(v, "%Y-%m-%d %H:%M:%S.%f").date()
|
||||
return None
|
||||
|
||||
@field_validator(
|
||||
"return_on_equity",
|
||||
"return_on_assets",
|
||||
"div_yield",
|
||||
"div_growth_3y",
|
||||
"div_growth_5y",
|
||||
"change_percent",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return round(float(v) / 100, 6) if v else None
|
||||
|
||||
|
||||
class TmxEquityQuoteFetcher(
|
||||
Fetcher[
|
||||
TmxEquityQuoteQueryParams,
|
||||
List[TmxEquityQuoteData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEquityQuoteQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxEquityQuoteQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEquityQuoteQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
symbols = query.symbol.split(",")
|
||||
|
||||
# The list where the results will be stored and appended to.
|
||||
results = []
|
||||
user_agent = get_random_agent()
|
||||
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
|
||||
async def create_task(symbol: str, results) -> None:
|
||||
"""Makes a POST request to the TMX GraphQL endpoint for a single symbol."""
|
||||
|
||||
symbol = (
|
||||
symbol.upper().replace("-", ".").replace(".TO", "").replace(".TSX", "")
|
||||
)
|
||||
|
||||
payload = gql.stock_info_payload.copy()
|
||||
payload["variables"]["symbol"] = symbol
|
||||
|
||||
data = {}
|
||||
r = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": f"https://money.tmx.com/en/quote/{symbol}",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=3,
|
||||
)
|
||||
if r["data"].get("getQuoteBySymbol"):
|
||||
data = r["data"]["getQuoteBySymbol"]
|
||||
results.append(data)
|
||||
else:
|
||||
_warn(f"Could not get data for {symbol}.")
|
||||
|
||||
tasks = [create_task(symbol, results) for symbol in symbols]
|
||||
await asyncio.gather(*tasks)
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEquityQuoteQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxEquityQuoteData]:
|
||||
"""Return the transformed data."""
|
||||
|
||||
# Remove the items associated with `equity.profile()`.
|
||||
items_list = [
|
||||
"shortDescription",
|
||||
"longDescription",
|
||||
"website",
|
||||
"phoneNumber",
|
||||
"fullAddress",
|
||||
"email",
|
||||
"issueType",
|
||||
"exchangeName",
|
||||
"employees",
|
||||
"exShortName",
|
||||
]
|
||||
data = [{k: v for k, v in d.items() if k not in items_list} for d in data]
|
||||
# Replace all NaN values with None.
|
||||
for d in data:
|
||||
for k, v in d.items():
|
||||
if v in (nan, 0, ""):
|
||||
d[k] = None
|
||||
# Sort the data by the order of the symbols in the query.
|
||||
symbols = query.symbol.split(",")
|
||||
symbol_to_index = {symbol: index for index, symbol in enumerate(symbols)}
|
||||
data = sorted(data, key=lambda d: symbol_to_index[d["symbol"]])
|
||||
|
||||
return [TmxEquityQuoteData.model_validate(d) for d in data]
|
||||
@@ -0,0 +1,71 @@
|
||||
"""TMX Equity Search fetcher."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.equity_search import (
|
||||
EquitySearchData,
|
||||
EquitySearchQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import get_all_tmx_companies
|
||||
from pandas import DataFrame
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class TmxEquitySearchQueryParams(EquitySearchQueryParams):
|
||||
"""TMX Equity Search query.
|
||||
|
||||
Source: https://www.tmx.com/
|
||||
"""
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request. The list of companies is cached for two days.",
|
||||
)
|
||||
|
||||
|
||||
class TmxEquitySearchData(EquitySearchData):
|
||||
"""TMX Equity Search Data."""
|
||||
|
||||
|
||||
class TmxEquitySearchFetcher(
|
||||
Fetcher[
|
||||
TmxEquitySearchQueryParams,
|
||||
List[TmxEquitySearchData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEquitySearchQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxEquitySearchQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEquitySearchQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
companies = await get_all_tmx_companies(use_cache=query.use_cache)
|
||||
results = DataFrame(index=companies, data=companies.values(), columns=["name"])
|
||||
results = results.reset_index().rename(columns={"index": "symbol"})
|
||||
|
||||
if query:
|
||||
results = results[
|
||||
results["name"].str.contains(query.query, case=False)
|
||||
| results["symbol"].str.contains(query.query, case=False)
|
||||
]
|
||||
|
||||
return results.reset_index(drop=True).astype(str).to_dict("records")
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEquitySearchQueryParams, data: List[Dict], **kwargs: Any
|
||||
) -> List[TmxEquitySearchData]:
|
||||
"""Transform the data to the standard format."""
|
||||
return [TmxEquitySearchData.model_validate(d) for d in data]
|
||||
109
openbb_platform/providers/tmx/openbb_tmx/models/etf_countries.py
Normal file
109
openbb_platform/providers/tmx/openbb_tmx/models/etf_countries.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""TMX ETF Countries fetcher."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.etf_countries import (
|
||||
EtfCountriesData,
|
||||
EtfCountriesQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import get_all_etfs
|
||||
from pandas import DataFrame
|
||||
from pydantic import Field
|
||||
|
||||
_warn = warnings.warn
|
||||
|
||||
|
||||
class TmxEtfCountriesQueryParams(EtfCountriesQueryParams):
|
||||
"""TMX ETF Countries Query Params"""
|
||||
|
||||
__json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request. All ETF data comes from a single JSON file that is updated daily."
|
||||
+ " To bypass, set to False. If True, the data will be cached for 4 hours.",
|
||||
)
|
||||
|
||||
|
||||
class TmxEtfCountriesData(EtfCountriesData):
|
||||
"""TMX ETF Countries Data."""
|
||||
|
||||
|
||||
class TmxEtfCountriesFetcher(
|
||||
Fetcher[
|
||||
TmxEtfCountriesQueryParams,
|
||||
List[TmxEtfCountriesData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEtfCountriesQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxEtfCountriesQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEtfCountriesQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
symbols = (
|
||||
query.symbol.split(",") if "," in query.symbol else [query.symbol.upper()]
|
||||
)
|
||||
|
||||
_data = DataFrame(await get_all_etfs(use_cache=query.use_cache))
|
||||
results = {}
|
||||
for symbol in symbols:
|
||||
data = {}
|
||||
if ".TO" in symbol:
|
||||
symbol = symbol.replace(".TO", "") # noqa
|
||||
_target = _data[_data["symbol"] == symbol]["regions"]
|
||||
target = DataFrame()
|
||||
if len(_target) > 0:
|
||||
target = DataFrame.from_records(_target.iloc[0]).rename(
|
||||
columns={"name": "country", "percent": "weight"}
|
||||
)
|
||||
if not target.empty:
|
||||
target = target.set_index("country")
|
||||
for i in target.index:
|
||||
data.update({i: target.loc[i]["weight"]})
|
||||
if data:
|
||||
results.update({symbol: data})
|
||||
else:
|
||||
_warn(f"No data found for {symbol}")
|
||||
|
||||
output = (
|
||||
DataFrame(results)
|
||||
.transpose()
|
||||
.reset_index()
|
||||
.rename(columns={"index": "symbol"})
|
||||
).transpose()
|
||||
output.columns = output.loc["symbol"].to_list()
|
||||
output.drop("symbol", axis=0, inplace=True)
|
||||
return (
|
||||
output.reset_index().rename(columns={"index": "country"}).to_dict("records")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEtfCountriesQueryParams, data: List[Dict], **kwargs: Any
|
||||
) -> List[TmxEtfCountriesData]:
|
||||
"""Return the transformed data."""
|
||||
output = DataFrame(data)
|
||||
for col in output.columns.to_list():
|
||||
if col != "country":
|
||||
output[col] = output[col].astype(float) / 100
|
||||
output = output.fillna(value="N/A").replace("N/A", None)
|
||||
output["country"] = (
|
||||
output["country"].astype(str).str.lower().str.replace(" ", "_")
|
||||
)
|
||||
return [
|
||||
TmxEtfCountriesData.model_validate(d) for d in output.to_dict("records")
|
||||
]
|
||||
142
openbb_platform/providers/tmx/openbb_tmx/models/etf_holdings.py
Normal file
142
openbb_platform/providers/tmx/openbb_tmx/models/etf_holdings.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""TMX ETF Holdings fetcher."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import pandas as pd
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.etf_holdings import (
|
||||
EtfHoldingsData,
|
||||
EtfHoldingsQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import get_all_etfs
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxEtfHoldingsQueryParams(EtfHoldingsQueryParams):
|
||||
"""TMX ETF Holdings query.
|
||||
|
||||
Source: https://www.tmx.com/
|
||||
"""
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request. All ETF data comes from a single JSON file that is updated daily."
|
||||
+ " To bypass, set to False. If True, the data will be cached for 4 hours.",
|
||||
)
|
||||
|
||||
|
||||
class TmxEtfHoldingsData(EtfHoldingsData):
|
||||
"""TMX ETF Holdings Data."""
|
||||
|
||||
symbol: Optional[str] = Field(
|
||||
description="The ticker symbol of the asset.", default=None
|
||||
)
|
||||
name: Optional[str] = Field(description="The name of the asset.", default=None)
|
||||
weight: Optional[float] = Field(
|
||||
description="The weight of the asset in the portfolio, as a normalized percentage.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
shares: Optional[Union[int, str]] = Field(
|
||||
description="The value of the assets under management.",
|
||||
alias="number_of_shares",
|
||||
default=None,
|
||||
)
|
||||
market_value: Optional[Union[float, str]] = Field(
|
||||
description="The market value of the holding.", default=None
|
||||
)
|
||||
currency: Optional[str] = Field(description="The currency of the holding.")
|
||||
share_percentage: Optional[float] = Field(
|
||||
description="The share percentage of the holding, as a normalized percentage.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
share_change: Optional[Union[float, str]] = Field(
|
||||
description="The change in shares of the holding.", default=None
|
||||
)
|
||||
country: Optional[str] = Field(
|
||||
description="The country of the holding.", default=None
|
||||
)
|
||||
exchange: Optional[str] = Field(
|
||||
description="The exchange code of the holding.", default=None
|
||||
)
|
||||
type_id: Optional[str] = Field(
|
||||
description="The holding type ID of the asset.", default=None
|
||||
)
|
||||
fund_id: Optional[str] = Field(
|
||||
description="The fund ID of the asset.", default=None
|
||||
)
|
||||
|
||||
@field_validator("share_percentage", "weight", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return float(v) / 100 if v else None
|
||||
|
||||
|
||||
class TmxEtfHoldingsFetcher(
|
||||
Fetcher[
|
||||
TmxEtfHoldingsQueryParams,
|
||||
List[TmxEtfHoldingsData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEtfHoldingsQueryParams:
|
||||
"""Transform the query."""
|
||||
params["symbol"] = (
|
||||
params["symbol"].replace(".TO", "").replace(".TSX", "").replace("-", ".")
|
||||
)
|
||||
return TmxEtfHoldingsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEtfHoldingsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
query.symbol = query.symbol.upper()
|
||||
results = []
|
||||
etf = pd.DataFrame()
|
||||
etfs = pd.DataFrame(await get_all_etfs(use_cache=query.use_cache))
|
||||
etf = etfs[etfs["symbol"] == query.symbol]
|
||||
|
||||
if len(etf) == 1:
|
||||
top_holdings = pd.DataFrame(etf["holdings_top10"].iloc[0])
|
||||
top_holdings = top_holdings.dropna(axis=1, how="all")
|
||||
_columns = {
|
||||
"numberofshares": "number_of_shares",
|
||||
"symbol": "symbol",
|
||||
"country": "country",
|
||||
"fundid": "fund_id",
|
||||
"excode": "exchange",
|
||||
"securityname": "name",
|
||||
"currency": "currency",
|
||||
"marketvalue": "market_value",
|
||||
"detailholdingtypeid": "type_id",
|
||||
"weighting": "weight",
|
||||
"sharepercentage": "share_percentage",
|
||||
"sharechange": "share_change",
|
||||
"shareChange": "share_change",
|
||||
}
|
||||
top_holdings.rename(columns=_columns, inplace=True)
|
||||
results = (
|
||||
top_holdings.fillna("N/A")
|
||||
.replace("NA", None)
|
||||
.replace("N/A", None)
|
||||
.to_dict("records")
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEtfHoldingsQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxEtfHoldingsData]:
|
||||
"""Transform the data to the standard format."""
|
||||
return [TmxEtfHoldingsData.model_validate(d) for d in data]
|
||||
233
openbb_platform/providers/tmx/openbb_tmx/models/etf_info.py
Normal file
233
openbb_platform/providers/tmx/openbb_tmx/models/etf_info.py
Normal file
@@ -0,0 +1,233 @@
|
||||
"""TMX ETF Info fetcher."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pandas as pd
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.etf_info import (
|
||||
EtfInfoData,
|
||||
EtfInfoQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import get_all_etfs
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxEtfInfoQueryParams(EtfInfoQueryParams):
|
||||
"""TMX ETF Info Query Params"""
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request. All ETF data comes from a single JSON file that is updated daily."
|
||||
+ " To bypass, set to False. If True, the data will be cached for 4 hours.",
|
||||
)
|
||||
|
||||
|
||||
class TmxEtfInfoData(EtfInfoData):
|
||||
"""TMX ETF Info Data."""
|
||||
|
||||
issuer: Optional[str] = Field(
|
||||
description="The issuer of the ETF.", alias="fund_family", default=None
|
||||
)
|
||||
investment_style: Optional[str] = Field(
|
||||
description="The investment style of the ETF.", default=None
|
||||
)
|
||||
esg: Optional[bool] = Field(
|
||||
description="Whether the ETF qualifies as an ESG fund.", default=None
|
||||
)
|
||||
currency: Optional[str] = Field(description="The currency of the ETF.")
|
||||
unit_price: Optional[float] = Field(
|
||||
description="The unit price of the ETF.", default=None
|
||||
)
|
||||
close: Optional[float] = Field(description="The closing price of the ETF.")
|
||||
prev_close: Optional[float] = Field(
|
||||
description="The previous closing price of the ETF.", default=None
|
||||
)
|
||||
return_1m: Optional[float] = Field(
|
||||
description="The one-month return of the ETF, as a normalized percent",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_3m: Optional[float] = Field(
|
||||
description="The three-month return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_6m: Optional[float] = Field(
|
||||
description="The six-month return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_ytd: Optional[float] = Field(
|
||||
description="The year-to-date return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_1y: Optional[float] = Field(
|
||||
description="The one-year return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_3y: Optional[float] = Field(
|
||||
description="The three-year return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_5y: Optional[float] = Field(
|
||||
description="The five-year return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_10y: Optional[float] = Field(
|
||||
description="The ten-year return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_from_inception: Optional[float] = Field(
|
||||
description="The return from inception of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
avg_volume: Optional[int] = Field(
|
||||
description="The average daily volume of the ETF.",
|
||||
alias="volume_avg_daily",
|
||||
default=None,
|
||||
)
|
||||
avg_volume_30d: Optional[int] = Field(
|
||||
description="The 30-day average volume of the ETF.",
|
||||
alias="volume_avg_30d",
|
||||
default=None,
|
||||
)
|
||||
aum: Optional[float] = Field(description="The AUM of the ETF.", default=None)
|
||||
pe_ratio: Optional[float] = Field(
|
||||
description="The price-to-earnings ratio of the ETF.", default=None
|
||||
)
|
||||
pb_ratio: Optional[float] = Field(
|
||||
description="The price-to-book ratio of the ETF.", default=None
|
||||
)
|
||||
management_fee: Optional[float] = Field(
|
||||
description="The management fee of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
mer: Optional[float] = Field(
|
||||
description="The management expense ratio of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
distribution_yield: Optional[float] = Field(
|
||||
description="The distribution yield of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
dividend_frequency: Optional[str] = Field(
|
||||
description="The dividend payment frequency of the ETF.", default=None
|
||||
)
|
||||
website: Optional[str] = Field(description="The website of the ETF.", default=None)
|
||||
description: Optional[str] = Field(
|
||||
description="The description of the ETF.",
|
||||
alias="investment_objectives",
|
||||
default=None,
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"distribution_yield",
|
||||
"return_1m",
|
||||
"return_3m",
|
||||
"return_6m",
|
||||
"return_ytd",
|
||||
"return_1y",
|
||||
"return_3y",
|
||||
"return_5y",
|
||||
"return_10y",
|
||||
"return_from_inception",
|
||||
"mer",
|
||||
"management_fee",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return float(v) / 100 if v else None
|
||||
|
||||
|
||||
class TmxEtfInfoFetcher(
|
||||
Fetcher[
|
||||
TmxEtfInfoQueryParams,
|
||||
List[TmxEtfInfoData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEtfInfoQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxEtfInfoQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEtfInfoQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
results = []
|
||||
symbols = (
|
||||
query.symbol.split(",") if "," in query.symbol else [query.symbol.upper()]
|
||||
)
|
||||
_data = pd.DataFrame(await get_all_etfs(use_cache=query.use_cache))
|
||||
COLUMNS = [
|
||||
"symbol",
|
||||
"inception_date",
|
||||
"name",
|
||||
"fund_family",
|
||||
"investment_style",
|
||||
"esg",
|
||||
"currency",
|
||||
"unit_price",
|
||||
"close",
|
||||
"prev_close",
|
||||
"return_1m",
|
||||
"return_3m",
|
||||
"return_6m",
|
||||
"return_ytd",
|
||||
"return_1y",
|
||||
"return_3y",
|
||||
"return_5y",
|
||||
"return_from_inception",
|
||||
"volume_avg_daily",
|
||||
"volume_avg_30d",
|
||||
"aum",
|
||||
"pe_ratio",
|
||||
"pb_ratio",
|
||||
"management_fee",
|
||||
"mer",
|
||||
"distribution_yield",
|
||||
"dividend_frequency",
|
||||
"website",
|
||||
"investment_objectives",
|
||||
]
|
||||
|
||||
for symbol in symbols:
|
||||
result = {}
|
||||
target = pd.DataFrame()
|
||||
symbol = ( # noqa: PLW2901
|
||||
symbol.replace(".TO", "").replace(".TSX", "").replace("-", ".")
|
||||
)
|
||||
target = _data[_data["symbol"] == symbol][COLUMNS]
|
||||
target = target.fillna("N/A").replace("N/A", None)
|
||||
if len(target) > 0:
|
||||
result = target.reset_index(drop=True).transpose().to_dict()[0]
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEtfInfoQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxEtfInfoData]:
|
||||
"""Return the transformed data."""
|
||||
return [TmxEtfInfoData.model_validate(d) for d in data]
|
||||
266
openbb_platform/providers/tmx/openbb_tmx/models/etf_search.py
Normal file
266
openbb_platform/providers/tmx/openbb_tmx/models/etf_search.py
Normal file
@@ -0,0 +1,266 @@
|
||||
"""TMX ETF Search fetcher."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
import pandas as pd
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.etf_search import (
|
||||
EtfSearchData,
|
||||
EtfSearchQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import get_all_etfs
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxEtfSearchQueryParams(EtfSearchQueryParams):
|
||||
"""TMX ETF Search query.
|
||||
|
||||
Source: https://www.tmx.com/
|
||||
"""
|
||||
|
||||
div_freq: Optional[Literal["monthly", "annually", "quarterly"]] = Field(
|
||||
description="The dividend payment frequency.", default=None
|
||||
)
|
||||
|
||||
sort_by: Optional[
|
||||
Literal[
|
||||
"nav",
|
||||
"return_1m",
|
||||
"return_3m",
|
||||
"return_6m",
|
||||
"return_1y",
|
||||
"return_3y",
|
||||
"return_ytd",
|
||||
"beta_1y",
|
||||
"volume_avg_daily",
|
||||
"management_fee",
|
||||
"distribution_yield",
|
||||
"pb_ratio",
|
||||
"pe_ratio",
|
||||
]
|
||||
] = Field(description="The column to sort by.", default=None)
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request. All ETF data comes from a single JSON file that is updated daily."
|
||||
+ " To bypass, set to False. If True, the data will be cached for 4 hours.",
|
||||
)
|
||||
|
||||
|
||||
class TmxEtfSearchData(EtfSearchData):
|
||||
"""TMX ETF Search Data."""
|
||||
|
||||
short_name: Optional[str] = Field(
|
||||
description="The short name of the ETF.", default=None
|
||||
)
|
||||
inception_date: Optional[str] = Field(
|
||||
description="The inception date of the ETF.", default=None
|
||||
)
|
||||
issuer: Optional[str] = Field(
|
||||
description="The issuer of the ETF.", alias="fund_family", default=None
|
||||
)
|
||||
investment_style: Optional[str] = Field(
|
||||
description="The investment style of the ETF.", default=None
|
||||
)
|
||||
esg: Optional[bool] = Field(
|
||||
description="Whether the ETF qualifies as an ESG fund.", default=None
|
||||
)
|
||||
currency: Optional[str] = Field(description="The currency of the ETF.")
|
||||
unit_price: Optional[float] = Field(
|
||||
description="The unit price of the ETF.", default=None
|
||||
)
|
||||
close: Optional[float] = Field(description="The closing price of the ETF.")
|
||||
prev_close: Optional[float] = Field(
|
||||
description="The previous closing price of the ETF.", default=None
|
||||
)
|
||||
return_1m: Optional[float] = Field(
|
||||
description="The one-month return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_3m: Optional[float] = Field(
|
||||
description="The three-month return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_6m: Optional[float] = Field(
|
||||
description="The six-month return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_ytd: Optional[float] = Field(
|
||||
description="The year-to-date return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_1y: Optional[float] = Field(
|
||||
description="The one-year return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
beta_1y: Optional[float] = Field(
|
||||
description="The one-year beta of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_3y: Optional[float] = Field(
|
||||
description="The three-year return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
beta_3y: Optional[float] = Field(
|
||||
description="The three-year beta of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_5y: Optional[float] = Field(
|
||||
description="The five-year return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
beta_5y: Optional[float] = Field(
|
||||
description="The five-year beta of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_10y: Optional[float] = Field(
|
||||
description="The ten-year return of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
beta_10y: Optional[float] = Field(
|
||||
description="The ten-year beta of the ETF.", default=None
|
||||
)
|
||||
beta_15y: Optional[float] = Field(
|
||||
description="The fifteen-year beta of the ETF.", default=None
|
||||
)
|
||||
return_from_inception: Optional[float] = Field(
|
||||
description="The return from inception of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
avg_volume: Optional[int] = Field(
|
||||
description="The average daily volume of the ETF.",
|
||||
alias="volume_avg_daily",
|
||||
default=None,
|
||||
)
|
||||
avg_volume_30d: Optional[int] = Field(
|
||||
description="The 30-day average volume of the ETF.",
|
||||
alias="volume_avg_30d",
|
||||
default=None,
|
||||
)
|
||||
aum: Optional[float] = Field(description="The AUM of the ETF.", default=None)
|
||||
pe_ratio: Optional[float] = Field(
|
||||
description="The price-to-earnings ratio of the ETF.", default=None
|
||||
)
|
||||
pb_ratio: Optional[float] = Field(
|
||||
description="The price-to-book ratio of the ETF.", default=None
|
||||
)
|
||||
management_fee: Optional[float] = Field(
|
||||
description="The management fee of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
mer: Optional[float] = Field(
|
||||
description="The management expense ratio of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
distribution_yield: Optional[float] = Field(
|
||||
description="The distribution yield of the ETF, as a normalized percent.",
|
||||
default=None,
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
dividend_frequency: Optional[str] = Field(
|
||||
description="The dividend payment frequency of the ETF.", default=None
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"distribution_yield",
|
||||
"return_1m",
|
||||
"return_3m",
|
||||
"return_6m",
|
||||
"return_ytd",
|
||||
"return_1y",
|
||||
"return_3y",
|
||||
"return_5y",
|
||||
"return_10y",
|
||||
"return_from_inception",
|
||||
"mer",
|
||||
"management_fee",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
if v:
|
||||
return float(v) / 100
|
||||
return None
|
||||
|
||||
|
||||
class TmxEtfSearchFetcher(
|
||||
Fetcher[
|
||||
TmxEtfSearchQueryParams,
|
||||
List[TmxEtfSearchData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEtfSearchQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxEtfSearchQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEtfSearchQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
etfs = pd.DataFrame(await get_all_etfs(use_cache=query.use_cache))
|
||||
|
||||
if query.query:
|
||||
etfs = etfs[
|
||||
etfs["name"].str.contains(query.query, case=False)
|
||||
| etfs["short_name"].str.contains(query.query, case=False)
|
||||
| etfs["investment_style"].str.contains(query.query, case=False)
|
||||
| etfs["investment_objectives"].str.contains(query.query, case=False)
|
||||
| etfs["symbol"].str.contains(query.query, case=False)
|
||||
]
|
||||
|
||||
data = etfs.copy()
|
||||
|
||||
if query.div_freq:
|
||||
data = data[data["dividend_frequency"] == query.div_freq.capitalize()]
|
||||
|
||||
if query.sort_by:
|
||||
data = data.sort_values(by=query.sort_by, ascending=False)
|
||||
|
||||
data.drop(
|
||||
columns=[
|
||||
"sectors",
|
||||
"regions",
|
||||
"holdings_top10_summary",
|
||||
"holdings_top10",
|
||||
"additional_data",
|
||||
"website",
|
||||
"asset_class_id",
|
||||
"investment_objectives",
|
||||
],
|
||||
inplace=True,
|
||||
)
|
||||
data = data.dropna(how="all")
|
||||
return data.fillna("N/A").replace("N/A", None).to_dict("records")
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEtfSearchQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxEtfSearchData]:
|
||||
"""Transform the data to the standard format."""
|
||||
return [TmxEtfSearchData.model_validate(d) for d in data]
|
||||
@@ -0,0 +1,85 @@
|
||||
"""TMX ETF Sectors fetcher."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.etf_sectors import (
|
||||
EtfSectorsData,
|
||||
EtfSectorsQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import get_all_etfs
|
||||
from pandas import DataFrame
|
||||
from pydantic import Field
|
||||
|
||||
_warn = warnings.warn
|
||||
|
||||
|
||||
class TmxEtfSectorsQueryParams(EtfSectorsQueryParams):
|
||||
"""TMX ETF Sectors Query Params"""
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request. All ETF data comes from a single JSON file that is updated daily."
|
||||
+ " To bypass, set to False. If True, the data will be cached for 4 hours.",
|
||||
)
|
||||
|
||||
|
||||
class TmxEtfSectorsData(EtfSectorsData):
|
||||
"""TMX ETF Sectors Data."""
|
||||
|
||||
|
||||
class TmxEtfSectorsFetcher(
|
||||
Fetcher[
|
||||
TmxEtfSectorsQueryParams,
|
||||
List[TmxEtfSectorsData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxEtfSectorsQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxEtfSectorsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxEtfSectorsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
target = DataFrame()
|
||||
_data = DataFrame(await get_all_etfs(use_cache=query.use_cache))
|
||||
symbol = (
|
||||
query.symbol.upper()
|
||||
.replace("-", ".")
|
||||
.replace(".TO", "")
|
||||
.replace(".TSX", "")
|
||||
)
|
||||
_target = _data[_data["symbol"] == symbol]["sectors"]
|
||||
if len(_target) > 0:
|
||||
target = DataFrame.from_records(_target.iloc[0]).rename(
|
||||
columns={"name": "sector", "percent": "weight"}
|
||||
)
|
||||
return target.to_dict(orient="records")
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxEtfSectorsQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxEtfSectorsData]:
|
||||
"""Return the transformed data."""
|
||||
target = DataFrame(data)
|
||||
target["weight"] = target["weight"] / 100
|
||||
target["sector"] = (
|
||||
target["sector"].astype(str).str.lower().str.replace(" ", "_")
|
||||
)
|
||||
target = target.fillna(value="N/A").replace("N/A", None)
|
||||
return [
|
||||
TmxEtfSectorsData.model_validate(d)
|
||||
for d in target.to_dict(orient="records")
|
||||
]
|
||||
145
openbb_platform/providers/tmx/openbb_tmx/models/gainers.py
Normal file
145
openbb_platform/providers/tmx/openbb_tmx/models/gainers.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""TMX Equity Gainers Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import json
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.equity_performance import (
|
||||
EquityPerformanceData,
|
||||
EquityPerformanceQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
|
||||
STOCK_LISTS_DICT = {
|
||||
"dividend": "TOP_DIVIDEND",
|
||||
"energy": "TOP_ENERGY",
|
||||
"healthcare": "TOP_HEALTHCARE",
|
||||
"industrials": "TOP_INDUSTRIALS",
|
||||
"price_performer": "TOP_PRICE_PERFORMER",
|
||||
"rising_stars": "RISING_STARS",
|
||||
"real_estate": "TOP_REAL_ESTATE",
|
||||
"tech": "TOP_TECH",
|
||||
"utilities": "TOP_UTILITIES",
|
||||
"52w_high": "TOP_WEEK_52_HIGH",
|
||||
"volume": "TOP_VOLUME",
|
||||
}
|
||||
|
||||
STOCK_LISTS = Literal[
|
||||
"dividend",
|
||||
"energy",
|
||||
"healthcare",
|
||||
"industrials",
|
||||
"price_performer",
|
||||
"rising_stars",
|
||||
"real_estate",
|
||||
"tech",
|
||||
"utilities",
|
||||
"52w_high",
|
||||
"volume",
|
||||
]
|
||||
|
||||
|
||||
class TmxGainersQueryParams(EquityPerformanceQueryParams):
|
||||
"""TMX Gainers Query Params."""
|
||||
|
||||
category: STOCK_LISTS = Field(
|
||||
default="price_performer",
|
||||
description="The category of list to retrieve. Defaults to `price_performer`.",
|
||||
)
|
||||
|
||||
|
||||
class TmxGainersData(EquityPerformanceData):
|
||||
"""TMX Gainers Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"name": "longName",
|
||||
"change": "priceChange",
|
||||
"percent_change": "percentChange",
|
||||
"thirty_day_price_change": "30 Day Price Change",
|
||||
"dividend_yield": "Dividend Yield",
|
||||
"year_high": "52 Week High",
|
||||
"avg_volume_10d": "10 Day Avg. Volume",
|
||||
"ninety_day_price_change": "90 Day Price Change",
|
||||
}
|
||||
rank: int = Field(description="The rank of the stock in the list.")
|
||||
|
||||
@field_validator("percent_change", mode="after", check_fields=False)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return float(v) / 100 if v else 0
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def check_metric(cls, values):
|
||||
for k, v in values.items():
|
||||
if v is None or v == "-":
|
||||
values[k] = 0
|
||||
if k in ["Dividend Yield"]:
|
||||
values[k] = float(v) / 100 if v else None
|
||||
return values
|
||||
|
||||
|
||||
class TmxGainersFetcher(
|
||||
Fetcher[
|
||||
TmxGainersQueryParams,
|
||||
List[TmxGainersData],
|
||||
]
|
||||
):
|
||||
"""TMX Gainers Fetcher."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxGainersQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxGainersQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxGainersQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxGainersData]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
user_agent = get_random_agent()
|
||||
payload = gql.get_stock_list_payload.copy()
|
||||
payload["variables"]["stockListId"] = STOCK_LISTS_DICT[query.category]
|
||||
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
response = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": "https://money.tmx.com",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
if "errors" in response:
|
||||
raise EmptyDataError()
|
||||
results = response["data"]["stockList"].get("listItems")
|
||||
metric = response["data"]["stockList"].get("metricTitle")
|
||||
for i in range(len(results)): # pylint: disable=C0200
|
||||
if "metric" in results[i]:
|
||||
results[i][metric] = results[i]["metric"]
|
||||
del results[i]["metric"]
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxGainersQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxGainersData]:
|
||||
"""Transform the data to the model."""
|
||||
return [TmxGainersData.model_validate(d) for d in data]
|
||||
@@ -0,0 +1,108 @@
|
||||
"""TMX Stock Dividends Model"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import json
|
||||
from datetime import date as dateType
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.historical_dividends import (
|
||||
HistoricalDividendsData,
|
||||
HistoricalDividendsQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class TmxHistoricalDividendsQueryParams(HistoricalDividendsQueryParams):
|
||||
"""TMX Historical Dividends Query Params"""
|
||||
|
||||
|
||||
class TmxHistoricalDividendsData(HistoricalDividendsData):
|
||||
"""TMX Historical Dividends Data"""
|
||||
|
||||
__alias_dict__ = {
|
||||
"ex_dividend_date": "exDate",
|
||||
"record_date": "recordDate",
|
||||
"payment_date": "payableDate",
|
||||
"declaration_date": "declarationDate",
|
||||
}
|
||||
currency: Optional[str] = Field(
|
||||
default=None, description="The currency the dividend is paid in."
|
||||
)
|
||||
decalaration_date: Optional[dateType] = Field(
|
||||
default=None, description="The date of the announcement."
|
||||
)
|
||||
record_date: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="The record date of ownership for rights to the dividend.",
|
||||
)
|
||||
payment_date: Optional[dateType] = Field(
|
||||
default=None, description="The date the dividend is paid."
|
||||
)
|
||||
|
||||
|
||||
class TmxHistoricalDividendsFetcher(
|
||||
Fetcher[TmxHistoricalDividendsQueryParams, List[TmxHistoricalDividendsData]]
|
||||
):
|
||||
"""TMX Historical Dividends Fetcher"""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxHistoricalDividendsQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxHistoricalDividendsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxHistoricalDividendsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
user_agent = get_random_agent()
|
||||
symbol = (
|
||||
query.symbol.upper()
|
||||
.replace("-", ".")
|
||||
.replace(".TO", "")
|
||||
.replace(".TSX", "")
|
||||
)
|
||||
data = []
|
||||
payload = gql.historical_dividends_payload.copy()
|
||||
payload["variables"]["symbol"] = symbol
|
||||
payload["variables"]["batch"] = 500
|
||||
payload["variables"]["page"] = 1
|
||||
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
response = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": f"https://money.tmx.com/en/quote/{symbol}",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
try:
|
||||
if "data" in response and "dividends" in response["data"]:
|
||||
data = response["data"].get("dividends")
|
||||
data = sorted(data["dividends"], key=lambda d: d["exDate"])
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(e) from e
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxHistoricalDividendsQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxHistoricalDividendsData]:
|
||||
"""Return the transformed data."""
|
||||
return [TmxHistoricalDividendsData.model_validate(d) for d in data]
|
||||
@@ -0,0 +1,93 @@
|
||||
"""TMX Index Constituents Model"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.index_constituents import (
|
||||
IndexConstituentsData,
|
||||
IndexConstituentsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_tmx.utils.helpers import get_data_from_url, tmx_indices_backend
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxIndexConstituentsQueryParams(IndexConstituentsQueryParams):
|
||||
"""TMX Index Constituents Query Params."""
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request."
|
||||
+ " Index data is from a single JSON file, updated each day after close."
|
||||
+ " It is cached for one day. To bypass, set to False.",
|
||||
)
|
||||
|
||||
|
||||
class TmxIndexConstituentsData(IndexConstituentsData):
|
||||
"""TMX Index Constituents Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"market_value": "quotedmarketvalue",
|
||||
}
|
||||
|
||||
market_value: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The quoted market value of the asset.",
|
||||
)
|
||||
|
||||
@field_validator("weight", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return float(v) / 100 if v else None
|
||||
|
||||
|
||||
class TmxIndexConstituentsFetcher(
|
||||
Fetcher[
|
||||
TmxIndexConstituentsQueryParams,
|
||||
List[TmxIndexConstituentsData],
|
||||
]
|
||||
):
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxIndexConstituentsQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxIndexConstituentsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxIndexConstituentsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> Dict:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
url = "https://tmxinfoservices.com/files/indices/sptsx-indices.json"
|
||||
|
||||
data = await get_data_from_url(
|
||||
url,
|
||||
use_cache=query.use_cache,
|
||||
backend=tmx_indices_backend,
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxIndexConstituentsQueryParams, data: Dict, **kwargs
|
||||
) -> List[TmxIndexConstituentsData]:
|
||||
"""Return the transformed data."""
|
||||
results = []
|
||||
data = data.copy()
|
||||
if data == {}:
|
||||
raise EmptyDataError
|
||||
if query.symbol not in data.get("indices"): # type: ignore
|
||||
raise ValueError(f"Index {query.symbol} was not found. Check the symbol.")
|
||||
index_data = data["indices"][query.symbol]
|
||||
if (
|
||||
index_data.get("nb_constituents") == 0
|
||||
or index_data.get("constituents") is None
|
||||
):
|
||||
raise ValueError(f"No constituents found for index, {query.symbol}")
|
||||
results = index_data["constituents"]
|
||||
return [TmxIndexConstituentsData.model_validate(d) for d in results]
|
||||
@@ -0,0 +1,90 @@
|
||||
"""TMX Index Sectors fetcher."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.index_sectors import (
|
||||
IndexSectorsData,
|
||||
IndexSectorsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_tmx.utils.helpers import get_data_from_url, tmx_indices_backend
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxIndexSectorsQueryParams(IndexSectorsQueryParams):
|
||||
"""TMX Index Sectors Query Params"""
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request. All Index data comes from a single JSON file that is updated daily."
|
||||
+ " To bypass, set to False. If True, the data will be cached for 1 day.",
|
||||
)
|
||||
|
||||
|
||||
class TmxIndexSectorsData(IndexSectorsData):
|
||||
"""TMX Index Sectors Data."""
|
||||
|
||||
@field_validator("weight", mode="after", check_fields=False)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return float(v) / 100 if v else None
|
||||
|
||||
|
||||
class TmxIndexSectorsFetcher(
|
||||
Fetcher[
|
||||
TmxIndexSectorsQueryParams,
|
||||
List[TmxIndexSectorsData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxIndexSectorsQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxIndexSectorsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxIndexSectorsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> Dict:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
url = "https://tmxinfoservices.com/files/indices/sptsx-indices.json"
|
||||
|
||||
data = await get_data_from_url(
|
||||
url,
|
||||
use_cache=query.use_cache,
|
||||
backend=tmx_indices_backend,
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxIndexSectorsQueryParams, data: Dict, **kwargs: Any
|
||||
) -> List[TmxIndexSectorsData]:
|
||||
"""Return the transformed data."""
|
||||
results = []
|
||||
data = data.copy()
|
||||
if data == {}:
|
||||
raise EmptyDataError
|
||||
if (
|
||||
query.symbol in data["indices"]
|
||||
and "sectors" in data["indices"][query.symbol]
|
||||
):
|
||||
temp = data["indices"][query.symbol].get("sectors")
|
||||
results = [
|
||||
{
|
||||
"sector": d.get("name").lower().replace(" ", "_"),
|
||||
"weight": d.get("weight"),
|
||||
}
|
||||
for d in temp
|
||||
if temp is not None
|
||||
]
|
||||
|
||||
return [TmxIndexSectorsData.model_validate(d) for d in results]
|
||||
@@ -0,0 +1,283 @@
|
||||
"""TMX Index Snapshots Model"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import json
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.index_snapshots import (
|
||||
IndexSnapshotsData,
|
||||
IndexSnapshotsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import (
|
||||
NASDAQ_GIDS,
|
||||
get_data_from_gql,
|
||||
get_data_from_url,
|
||||
get_random_agent,
|
||||
tmx_indices_backend,
|
||||
)
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxIndexSnapshotsQueryParams(IndexSnapshotsQueryParams):
|
||||
"""TMX Index Snapshots Query Params."""
|
||||
|
||||
region: Literal[None, "ca", "us"] = Field(default="ca")
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use a cached request."
|
||||
+ " Index data is from a single JSON file, updated each day after close."
|
||||
+ " It is cached for one day. To bypass, set to False.",
|
||||
)
|
||||
|
||||
|
||||
class TmxIndexSnapshotsData(IndexSnapshotsData):
|
||||
"""TMX Index Snapshots Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"name": "longname",
|
||||
"prev_close": "prevClose",
|
||||
"change": "priceChange",
|
||||
"change_percent": "previousday",
|
||||
"year_high": "weeks52high",
|
||||
"year_low": "weeks52low",
|
||||
"return_mtd": "monthtodate",
|
||||
"return_qtd": "quartertodate",
|
||||
"return_ytd": "yeartodate",
|
||||
"total_market_value": "total",
|
||||
"constituent_average_market_value": "average",
|
||||
"constituent_median_market_value": "median",
|
||||
"constituent_top10_market_value": "sumtop10",
|
||||
"constituent_largest_market_value": "largest",
|
||||
"constituent_largest_weight": "largestweight",
|
||||
"constituent_smallest_market_value": "smallest",
|
||||
"constituent_smallest_weight": "smallestweight",
|
||||
}
|
||||
year_high: Optional[float] = Field(
|
||||
default=None, description="The 52-week high of the index."
|
||||
)
|
||||
year_low: Optional[float] = Field(
|
||||
default=None, description="The 52-week low of the index."
|
||||
)
|
||||
return_mtd: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The month-to-date return of the index, as a normalized percent.",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_qtd: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The quarter-to-date return of the index, as a normalized percent.",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
return_ytd: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The year-to-date return of the index, as a normalized percent.",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
total_market_value: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The total quoted market value of the index.",
|
||||
)
|
||||
number_of_constituents: Optional[int] = Field(
|
||||
default=None,
|
||||
description="The number of constituents in the index.",
|
||||
)
|
||||
constituent_average_market_value: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The average quoted market value of the index constituents.",
|
||||
)
|
||||
constituent_median_market_value: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The median quoted market value of the index constituents.",
|
||||
)
|
||||
constituent_top10_market_value: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The sum of the top 10 quoted market values of the index constituents.",
|
||||
)
|
||||
constituent_largest_market_value: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The largest quoted market value of the index constituents.",
|
||||
)
|
||||
constituent_largest_weight: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The largest weight of the index constituents, as a normalized percent.",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
constituent_smallest_market_value: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The smallest quoted market value of the index constituents.",
|
||||
)
|
||||
constituent_smallest_weight: Optional[float] = Field(
|
||||
default=None,
|
||||
description="The smallest weight of the index constituents, as a normalized percent.",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
|
||||
@field_validator(
|
||||
"return_mtd",
|
||||
"return_qtd",
|
||||
"return_ytd",
|
||||
"change_percent",
|
||||
"constituent_largest_weight",
|
||||
"constituent_smallest_weight",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return round(float(v) / 100, 6) if v else None
|
||||
|
||||
@field_validator(
|
||||
"year_high",
|
||||
"year_low",
|
||||
"price",
|
||||
"prev_close",
|
||||
"change",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def round_floating(cls, v):
|
||||
"""Round floating values to two decimals."""
|
||||
return round(float(v), 2) if v else None
|
||||
|
||||
|
||||
class TmxIndexSnapshotsFetcher(
|
||||
Fetcher[
|
||||
TmxIndexSnapshotsQueryParams,
|
||||
List[TmxIndexSnapshotsData],
|
||||
]
|
||||
):
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxIndexSnapshotsQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxIndexSnapshotsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxIndexSnapshotsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
url = "https://tmxinfoservices.com/files/indices/sptsx-indices.json"
|
||||
user_agent = get_random_agent()
|
||||
results = []
|
||||
if query.region == "ca":
|
||||
data = await get_data_from_url(
|
||||
url,
|
||||
use_cache=query.use_cache,
|
||||
backend=tmx_indices_backend,
|
||||
)
|
||||
if not data:
|
||||
raise EmptyDataError
|
||||
symbols = []
|
||||
|
||||
for symbol in data["indices"]:
|
||||
symbols.append(symbol)
|
||||
new_data = {}
|
||||
performance = data["indices"][symbol].get("performance", {})
|
||||
market_value = data["indices"][symbol].get("quotedmarketvalue", {})
|
||||
new_data.update(
|
||||
{
|
||||
"symbol": symbol,
|
||||
"name": data["indices"][symbol].get("name_en", None),
|
||||
"currency": (
|
||||
"USD"
|
||||
if "(USD)" in data["indices"][symbol]["name_en"]
|
||||
else "CAD"
|
||||
),
|
||||
**performance,
|
||||
**market_value,
|
||||
}
|
||||
)
|
||||
results.append(new_data)
|
||||
|
||||
# Get current levels for each index.
|
||||
|
||||
payload = gql.get_quote_for_symbols_payload.copy()
|
||||
payload["variables"]["symbols"] = symbols
|
||||
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
response = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": "https://money.tmx.com/en/quote/^TSX",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
if response.get("data") and response["data"].get("getQuoteForSymbols"):
|
||||
quote_data = response["data"]["getQuoteForSymbols"]
|
||||
for d in data:
|
||||
if "longname" in d:
|
||||
d.pop("longname")
|
||||
if "percentChange" in d:
|
||||
d.pop("percentChange")
|
||||
merged_list = [
|
||||
{
|
||||
**d1,
|
||||
**next(
|
||||
(d2 for d2 in quote_data if d2["symbol"] == d1["symbol"]),
|
||||
{},
|
||||
),
|
||||
}
|
||||
for d1 in results
|
||||
]
|
||||
results = merged_list
|
||||
|
||||
if query.region == "us":
|
||||
symbols = [f"{symbol}:US" for symbol in NASDAQ_GIDS]
|
||||
payload = gql.get_quote_for_symbols_payload.copy()
|
||||
payload["variables"]["symbols"] = symbols
|
||||
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
response = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": "https://money.tmx.com/en/quote/^TSX",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
if response.get("data") and response["data"].get("getQuoteForSymbols"):
|
||||
results = response["data"]["getQuoteForSymbols"]
|
||||
for item in results:
|
||||
item["change_percent"] = item.pop("percentChange")
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxIndexSnapshotsQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxIndexSnapshotsData]:
|
||||
"""Return the transformed data."""
|
||||
return [
|
||||
TmxIndexSnapshotsData.model_validate(
|
||||
{
|
||||
"change_percent" if k == "percentChange" else k: (
|
||||
None if v in ["", 0] else v
|
||||
)
|
||||
for k, v in d.items()
|
||||
}
|
||||
)
|
||||
for d in data
|
||||
if "price" in d and d["price"] is not None
|
||||
]
|
||||
@@ -0,0 +1,168 @@
|
||||
"""TMX Insider Trading Model"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import json
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.insider_trading import (
|
||||
InsiderTradingData,
|
||||
InsiderTradingQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_core.provider.utils.helpers import to_snake_case
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxInsiderTradingQueryParams(InsiderTradingQueryParams):
|
||||
"""TMX Insider Trading Query Params"""
|
||||
|
||||
summary: bool = Field(
|
||||
default=False,
|
||||
description="Return a summary of the insider activity instead of the individuals.",
|
||||
)
|
||||
|
||||
|
||||
class TmxInsiderTradingData(InsiderTradingData):
|
||||
"""TMX Insider Trading Data"""
|
||||
|
||||
period: str = Field(
|
||||
description="The period of the activity. Bucketed by three, six, and twelve months."
|
||||
)
|
||||
owner_name: Optional[str] = Field(
|
||||
default=None, description="The name of the insider."
|
||||
)
|
||||
acquisition_or_deposition: Optional[str] = Field(
|
||||
default=None, description="Whether the insider bought or sold the shares."
|
||||
)
|
||||
number_of_trades: Optional[int] = Field(
|
||||
default=None, description="The number of shares traded over the period."
|
||||
)
|
||||
securities_owned: Optional[int] = Field(
|
||||
default=None, description="The number of shares held by the insider."
|
||||
)
|
||||
trade_value: Optional[float] = Field(
|
||||
default=None, description="The value of the shares traded by the insider."
|
||||
)
|
||||
securities_transacted: Optional[int] = Field(
|
||||
default=None,
|
||||
description="The total number of shares traded by the insider over the period.",
|
||||
)
|
||||
securities_bought: Optional[int] = Field(
|
||||
default=None,
|
||||
description="The total number of shares bought by all insiders over the period.",
|
||||
)
|
||||
securities_sold: Optional[int] = Field(
|
||||
default=None,
|
||||
description="The total number of shares sold by all insiders over the period.",
|
||||
)
|
||||
net_activity: Optional[int] = Field(
|
||||
default=None,
|
||||
description="The total net activity by all insiders over the period.",
|
||||
)
|
||||
|
||||
@field_validator("period", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def period_to_snake_case(cls, v):
|
||||
return to_snake_case(v) if v else None
|
||||
|
||||
|
||||
class TmxInsiderTradingFetcher(
|
||||
Fetcher[
|
||||
TmxInsiderTradingQueryParams,
|
||||
List[TmxInsiderTradingData],
|
||||
]
|
||||
):
|
||||
"""TMX Insider Trading Fetcher."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxInsiderTradingQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxInsiderTradingQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxInsiderTradingQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
|
||||
results = []
|
||||
user_agent = get_random_agent()
|
||||
symbol = (
|
||||
query.symbol.upper()
|
||||
.replace("-", ".")
|
||||
.replace(".TO", "")
|
||||
.replace(".TSX", "")
|
||||
)
|
||||
payload = gql.get_company_insiders_payload.copy()
|
||||
payload["variables"]["symbol"] = symbol
|
||||
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
response = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": f"https://money.tmx.com/en/quote/{symbol}",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
if response.get("data") and response["data"].get(
|
||||
"getCompanyInsidersActivities"
|
||||
):
|
||||
results = response["data"]["getCompanyInsidersActivities"]
|
||||
|
||||
if results == []:
|
||||
raise EmptyDataError()
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxInsiderTradingQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxInsiderTradingData]:
|
||||
"""Transform the data."""
|
||||
data = data.copy()
|
||||
results = []
|
||||
flattened_insiders = []
|
||||
for activity in data["insiderActivities"]: # type: ignore
|
||||
for transaction_type in ["buy", "sell"]:
|
||||
for transaction in activity[transaction_type]:
|
||||
new_transaction = {
|
||||
"period": activity["periodkey"],
|
||||
"acquisition_or_disposition": transaction_type,
|
||||
"owner_name": transaction["name"],
|
||||
"number_of_trades": transaction["trades"],
|
||||
"securities_transacted": transaction["shares"],
|
||||
"securities_owned": transaction["sharesHeld"],
|
||||
"trade_value": transaction["tradeValue"],
|
||||
}
|
||||
flattened_insiders.append(new_transaction)
|
||||
flattened_summary = []
|
||||
for activity in data["activitySummary"]: # type: ignore
|
||||
new_activity = {
|
||||
"period": activity["periodkey"],
|
||||
"securities_bought": activity["buyShares"],
|
||||
"securities_sold": activity["soldShares"],
|
||||
"net_activity": activity["netActivity"],
|
||||
"securities_transacted": activity["totalShares"],
|
||||
}
|
||||
flattened_summary.append(new_activity)
|
||||
if query.summary is False and len(flattened_insiders) > 0:
|
||||
results = flattened_insiders
|
||||
elif query.summary is True and len(flattened_summary) > 0:
|
||||
results = flattened_summary
|
||||
|
||||
return [TmxInsiderTradingData.model_validate(d) for d in results]
|
||||
@@ -0,0 +1,105 @@
|
||||
"""TMX Options Chains Model."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
)
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.options_chains import (
|
||||
OptionsChainsData,
|
||||
OptionsChainsQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.descriptions import (
|
||||
QUERY_DESCRIPTIONS,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import download_eod_chains, get_current_options
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxOptionsChainsQueryParams(OptionsChainsQueryParams):
|
||||
"""TMX Options Chains Query.
|
||||
|
||||
Source: https://www.Tmx.com/
|
||||
"""
|
||||
|
||||
date: Optional[dateType] = Field(
|
||||
description=QUERY_DESCRIPTIONS.get("date", ""),
|
||||
default=None,
|
||||
)
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Caching is used to validate the supplied ticker symbol, or if a historical EOD chain is requested."
|
||||
+ " To bypass, set to False.",
|
||||
)
|
||||
|
||||
|
||||
class TmxOptionsChainsData(OptionsChainsData):
|
||||
"""TMX Options Chains Data."""
|
||||
|
||||
transactions: Optional[int] = Field(
|
||||
description="Number of transactions for the contract.", default=None
|
||||
)
|
||||
total_value: Optional[float] = Field(
|
||||
description="Total value of the transactions.", default=None
|
||||
)
|
||||
settlement_price: Optional[float] = Field(
|
||||
description="Settlement price on that date.", default=None
|
||||
)
|
||||
underlying_price: Optional[float] = Field(
|
||||
description="Price of the underlying stock on that date.", default=None
|
||||
)
|
||||
dte: Optional[int] = Field(
|
||||
description="Days to expiration for the option.", default=None
|
||||
)
|
||||
|
||||
@field_validator("expiration", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def date_validate(cls, v): # pylint: disable=E0213
|
||||
"""Return the datetime object from the date string"""
|
||||
return datetime.strptime(v, "%Y-%m-%d")
|
||||
|
||||
|
||||
class TmxOptionsChainsFetcher(
|
||||
Fetcher[
|
||||
TmxOptionsChainsQueryParams,
|
||||
List[TmxOptionsChainsData],
|
||||
]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxOptionsChainsQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxOptionsChainsQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxOptionsChainsQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the data."""
|
||||
results = []
|
||||
if query.date is not None:
|
||||
chains = await download_eod_chains(
|
||||
symbol=query.symbol, date=query.date, use_cache=query.use_cache
|
||||
)
|
||||
else:
|
||||
chains = await get_current_options(query.symbol, use_cache=query.use_cache)
|
||||
|
||||
if not chains.empty:
|
||||
results = chains.to_dict(orient="records")
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxOptionsChainsQueryParams,
|
||||
data: dict,
|
||||
**kwargs: Any,
|
||||
) -> List[TmxOptionsChainsData]:
|
||||
"""Transform the data and validate the model."""
|
||||
return [TmxOptionsChainsData.model_validate(d) for d in data]
|
||||
@@ -0,0 +1,160 @@
|
||||
"""TMX Stock Analysts Model"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.price_target_consensus import (
|
||||
PriceTargetConsensusData,
|
||||
PriceTargetConsensusQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils import gql
|
||||
from openbb_tmx.utils.helpers import get_data_from_gql, get_random_agent
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxPriceTargetConsensusQueryParams(PriceTargetConsensusQueryParams):
|
||||
"""TMX Price Target Consensus Query."""
|
||||
|
||||
__json_schema_extra__ = {"symbol": ["multiple_items_allowed"]}
|
||||
|
||||
|
||||
class TmxPriceTargetConsensusData(PriceTargetConsensusData):
|
||||
"""TMX Price Target Consensus Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"target_consensus": "price_target",
|
||||
"target_high": "price_target_high",
|
||||
"target_low": "price_target_low",
|
||||
"target_upside": "price_target_upside",
|
||||
}
|
||||
|
||||
target_upside: Optional[float] = Field(
|
||||
default=None,
|
||||
description="Percent of upside, as a normalized percent.",
|
||||
json_schema_extra={"unit_measurement": "percent", "frontend_multiply": 100},
|
||||
)
|
||||
total_analysts: Optional[int] = Field(
|
||||
default=None, description="Total number of analyst."
|
||||
)
|
||||
buy_ratings: Optional[int] = Field(
|
||||
default=None, description="Number of buy ratings."
|
||||
)
|
||||
sell_ratings: Optional[int] = Field(
|
||||
default=None, description="Number of sell ratings."
|
||||
)
|
||||
hold_ratings: Optional[int] = Field(
|
||||
default=None, description="Number of hold ratings."
|
||||
)
|
||||
consensus_action: Optional[str] = Field(
|
||||
default=None, description="Consensus action."
|
||||
)
|
||||
|
||||
@field_validator("target_upside", mode="before", check_fields=False)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return float(v) / 100 if v else None
|
||||
|
||||
@field_validator(
|
||||
"target_consensus",
|
||||
"target_high",
|
||||
"target_low",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def round_targets(cls, v):
|
||||
"""Return rounded prices to two decimals."""
|
||||
return round(float(v), 2) if v else None
|
||||
|
||||
|
||||
class TmxPriceTargetConsensusFetcher(
|
||||
Fetcher[TmxPriceTargetConsensusQueryParams, List[TmxPriceTargetConsensusData]]
|
||||
):
|
||||
"""Transform the query, extract and transform the data from the TMX endpoints."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxPriceTargetConsensusQueryParams:
|
||||
"""Transform the query."""
|
||||
return TmxPriceTargetConsensusQueryParams(**params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxPriceTargetConsensusQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> List[Dict]:
|
||||
"""Return the raw data from the TMX endpoint."""
|
||||
symbols = query.symbol.split(",")
|
||||
results = []
|
||||
|
||||
async def create_task(symbol, results):
|
||||
"""Create a task for each symbol provided."""
|
||||
symbol = (
|
||||
symbol.upper()
|
||||
.replace("-", ".")
|
||||
.replace(".TO", "")
|
||||
.replace(".TSXV", "")
|
||||
.replace(".TSX", "")
|
||||
)
|
||||
|
||||
payload = gql.get_company_analysts_payload.copy()
|
||||
payload["variables"]["symbol"] = symbol
|
||||
payload["variables"]["datatype"] = "equity"
|
||||
|
||||
data = {}
|
||||
url = "https://app-money.tmx.com/graphql"
|
||||
response = await get_data_from_gql(
|
||||
method="POST",
|
||||
url=url,
|
||||
data=json.dumps(payload),
|
||||
headers={
|
||||
"authority": "app-money.tmx.com",
|
||||
"referer": f"https://money.tmx.com/en/quote/{symbol}",
|
||||
"locale": "en",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": get_random_agent(),
|
||||
"Accept": "*/*",
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
r_data = (
|
||||
response["data"].get("analysts", None) if response.get("data") else None
|
||||
)
|
||||
if r_data:
|
||||
data.update(
|
||||
{
|
||||
"symbol": symbol,
|
||||
"total_analysts": r_data["totalAnalysts"],
|
||||
"consensus_action": r_data["consensusAnalysts"]["consensus"],
|
||||
"buy_ratings": r_data["consensusAnalysts"]["buy"],
|
||||
"sell_ratings": r_data["consensusAnalysts"]["sell"],
|
||||
"hold_ratings": r_data["consensusAnalysts"]["hold"],
|
||||
"price_target": r_data["priceTarget"]["priceTarget"],
|
||||
"price_target_high": r_data["priceTarget"]["highPriceTarget"],
|
||||
"price_target_low": r_data["priceTarget"]["lowPriceTarget"],
|
||||
"price_target_upside": r_data["priceTarget"][
|
||||
"priceTargetUpside"
|
||||
],
|
||||
}
|
||||
)
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
tasks = [create_task(symbol, results) for symbol in symbols]
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxPriceTargetConsensusQueryParams,
|
||||
data: List[Dict],
|
||||
**kwargs: Any,
|
||||
) -> List[TmxPriceTargetConsensusData]:
|
||||
"""Return the transformed data."""
|
||||
return [TmxPriceTargetConsensusData.model_validate(d) for d in data]
|
||||
@@ -0,0 +1,156 @@
|
||||
"""TMX Treasury Prices Fetcher"""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
from datetime import (
|
||||
date as dateType,
|
||||
datetime,
|
||||
timedelta,
|
||||
)
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from openbb_core.provider.abstract.fetcher import Fetcher
|
||||
from openbb_core.provider.standard_models.treasury_prices import (
|
||||
TreasuryPricesData,
|
||||
TreasuryPricesQueryParams,
|
||||
)
|
||||
from openbb_tmx.utils.helpers import get_all_bonds
|
||||
from pandas import DataFrame
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
|
||||
class TmxTreasuryPricesQueryParams(TreasuryPricesQueryParams):
|
||||
"""
|
||||
TMX Treasury Prices Query Params.
|
||||
|
||||
Data will be made available by 5:00 EST on T+1
|
||||
|
||||
Source: https://bondtradedata.iiroc.ca/#/
|
||||
"""
|
||||
|
||||
govt_type: Literal["federal", "provincial", "municipal"] = Field(
|
||||
default="federal",
|
||||
description="The level of government issuer.",
|
||||
)
|
||||
issue_date_min: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Filter by the minimum original issue date.",
|
||||
)
|
||||
issue_date_max: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Filter by the maximum original issue date.",
|
||||
)
|
||||
last_traded_min: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Filter by the minimum last trade date.",
|
||||
)
|
||||
maturity_date_min: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Filter by the minimum maturity date.",
|
||||
)
|
||||
maturity_date_max: Optional[dateType] = Field(
|
||||
default=None,
|
||||
description="Filter by the maximum maturity date.",
|
||||
)
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="All bond data is sourced from a single JSON file that is updated daily."
|
||||
+ " The file is cached for one day to eliminate downloading more than once."
|
||||
+ " Caching will significantly speed up subsequent queries. To bypass, set to False.",
|
||||
)
|
||||
|
||||
|
||||
class TmxTreasuryPricesData(TreasuryPricesData):
|
||||
"""TMX Treasury Prices Data."""
|
||||
|
||||
__alias_dict__ = {
|
||||
"rate": "couponRate",
|
||||
"ytm": "lastYield",
|
||||
"last_price": "lastPrice",
|
||||
"highest_price": "highestPrice",
|
||||
"lowest_price": "lowestPrice",
|
||||
"total_trades": "totalTrades",
|
||||
"last_traded_date": "lastTradedDate",
|
||||
"maturity_date": "maturityDate",
|
||||
"issue_date": "originalIssueDate",
|
||||
"issuer_name": "issuer",
|
||||
}
|
||||
|
||||
@field_validator(
|
||||
"ytm",
|
||||
"rate",
|
||||
mode="before",
|
||||
check_fields=False,
|
||||
)
|
||||
@classmethod
|
||||
def normalize_percent(cls, v):
|
||||
"""Return percents as normalized percentage points."""
|
||||
return float(v) / 100 if v else None
|
||||
|
||||
|
||||
class TmxTreasuryPricesFetcher(
|
||||
Fetcher[
|
||||
TmxTreasuryPricesQueryParams,
|
||||
List[TmxTreasuryPricesData],
|
||||
]
|
||||
):
|
||||
"""Tmx Bond Reference Fetcher."""
|
||||
|
||||
@staticmethod
|
||||
def transform_query(params: Dict[str, Any]) -> TmxTreasuryPricesQueryParams:
|
||||
"""Transform query params."""
|
||||
transformed_params = params.copy()
|
||||
now = datetime.now()
|
||||
if now.date().weekday() > 4:
|
||||
now = now - timedelta(now.date().weekday() - 4)
|
||||
if "maturity_date_min" not in transformed_params:
|
||||
transformed_params["maturity_date_min"] = (
|
||||
now - timedelta(days=1)
|
||||
).strftime("%Y-%m-%d")
|
||||
return TmxTreasuryPricesQueryParams(**transformed_params)
|
||||
|
||||
@staticmethod
|
||||
async def aextract_data(
|
||||
query: TmxTreasuryPricesQueryParams,
|
||||
credentials: Optional[Dict[str, str]],
|
||||
**kwargs: Any,
|
||||
) -> DataFrame:
|
||||
"""Get the raw data containing all bond data."""
|
||||
bonds = await get_all_bonds(use_cache=query.use_cache)
|
||||
return bonds
|
||||
|
||||
@staticmethod
|
||||
def transform_data(
|
||||
query: TmxTreasuryPricesQueryParams,
|
||||
data: DataFrame,
|
||||
**kwargs: Any,
|
||||
) -> List[TmxTreasuryPricesData]:
|
||||
"""Transform data."""
|
||||
bonds = data.copy()
|
||||
results = []
|
||||
govt_type_dict = {
|
||||
"provincial": "prov",
|
||||
"federal": "government of canada",
|
||||
"municipal": "municipal",
|
||||
}
|
||||
govt_type = govt_type_dict[query.govt_type] # noqa # pylint: disable=W0612
|
||||
data = bonds.query(
|
||||
"maturityDate >= @query.maturity_date_min.strftime('%Y-%m-%d')"
|
||||
+ " & bondType == 'Govt'"
|
||||
+ " & issuer.str.contains(@govt_type, case=False)"
|
||||
).sort_values(by=["maturityDate"])
|
||||
data.issuer = data.loc[:, "issuer"].str.strip()
|
||||
if query.maturity_date_max:
|
||||
data = data.query(
|
||||
"maturityDate <= @query.maturity_date_max.strftime('%Y-%m-%d')"
|
||||
)
|
||||
if query.last_traded_min:
|
||||
data = data.query(
|
||||
"lastTradedDate >= @query.last_traded_min.strftime('%Y-%m-%d')"
|
||||
)
|
||||
|
||||
if len(data) > 0:
|
||||
data = data.drop(columns=["bondType", "securityId", "secKey"])
|
||||
data = data.fillna("N/A").replace("N/A", None)
|
||||
results = data.to_dict("records")
|
||||
|
||||
return [TmxTreasuryPricesData.model_validate(d) for d in results]
|
||||
0
openbb_platform/providers/tmx/openbb_tmx/py.typed
vendored
Normal file
0
openbb_platform/providers/tmx/openbb_tmx/py.typed
vendored
Normal file
513
openbb_platform/providers/tmx/openbb_tmx/utils/gql.py
Normal file
513
openbb_platform/providers/tmx/openbb_tmx/utils/gql.py
Normal file
@@ -0,0 +1,513 @@
|
||||
"""GraphQL query definitions."""
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
# ruff: noqa: E501
|
||||
|
||||
stock_info_query = """ query getQuoteBySymbol(
|
||||
$symbol: String,
|
||||
$locale: String
|
||||
) {
|
||||
getQuoteBySymbol(symbol: $symbol, locale: $locale) {
|
||||
symbol
|
||||
name
|
||||
price
|
||||
priceChange
|
||||
percentChange
|
||||
exchangeName
|
||||
exShortName
|
||||
exchangeCode
|
||||
sector
|
||||
industry
|
||||
volume
|
||||
openPrice
|
||||
dayHigh
|
||||
dayLow
|
||||
MarketCap
|
||||
MarketCapAllClasses
|
||||
peRatio
|
||||
prevClose
|
||||
dividendFrequency
|
||||
dividendYield
|
||||
dividendAmount
|
||||
dividendCurrency
|
||||
beta
|
||||
eps
|
||||
exDividendDate
|
||||
shortDescription
|
||||
longDescription
|
||||
website
|
||||
email
|
||||
phoneNumber
|
||||
fullAddress
|
||||
employees
|
||||
shareOutStanding
|
||||
totalDebtToEquity
|
||||
totalSharesOutStanding
|
||||
sharesESCROW
|
||||
vwap
|
||||
dividendPayDate
|
||||
weeks52high
|
||||
weeks52low
|
||||
alpha
|
||||
averageVolume10D
|
||||
averageVolume30D
|
||||
averageVolume50D
|
||||
priceToBook
|
||||
priceToCashFlow
|
||||
returnOnEquity
|
||||
returnOnAssets
|
||||
day21MovingAvg
|
||||
day50MovingAvg
|
||||
day200MovingAvg
|
||||
dividend3Years
|
||||
dividend5Years
|
||||
datatype
|
||||
issueType
|
||||
qmdescription
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
stock_info_payload = {
|
||||
"operationName": "getQuoteBySymbol",
|
||||
"variables": {"locale": "en"},
|
||||
"query": stock_info_query,
|
||||
}
|
||||
|
||||
get_timeseries_query = """query getTimeSeriesData($symbol: String!, $freq: String, $interval: Int, $start: String, $end: String, $startDateTime: Int, $endDateTime: Int) {
|
||||
getTimeSeriesData(
|
||||
symbol: $symbol
|
||||
freq: $freq
|
||||
interval: $interval
|
||||
start: $start
|
||||
end: $end
|
||||
startDateTime: $startDateTime
|
||||
endDateTime: $endDateTime
|
||||
) {
|
||||
dateTime
|
||||
open
|
||||
high
|
||||
low
|
||||
close
|
||||
volume
|
||||
}
|
||||
}"""
|
||||
|
||||
get_timeseries_payload = {
|
||||
"operationName": "getTimeSeriesData",
|
||||
"variables": {
|
||||
"symbol": "BNS",
|
||||
"freq": "day",
|
||||
"interval": "",
|
||||
"start": "2013-09-30",
|
||||
"end": "2013-10-31",
|
||||
"startDateTime": "",
|
||||
"endDateTime": "",
|
||||
},
|
||||
"query": get_timeseries_query,
|
||||
}
|
||||
|
||||
get_company_price_history_query = """query getCompanyPriceHistory($symbol: String!, $start: String, $end: String, $adjusted: Boolean, $adjustmentType: String, $unadjusted: Boolean, $limit: Int) {
|
||||
getCompanyPriceHistory(
|
||||
symbol: $symbol
|
||||
start: $start
|
||||
end: $end
|
||||
adjusted: $adjusted
|
||||
adjustmentType: $adjustmentType
|
||||
unadjusted: $unadjusted
|
||||
limit: $limit
|
||||
) {
|
||||
datetime
|
||||
openPrice
|
||||
closePrice
|
||||
high
|
||||
low
|
||||
volume
|
||||
tradeValue
|
||||
numberOfTrade
|
||||
change
|
||||
changePercent
|
||||
vwap
|
||||
}
|
||||
}"""
|
||||
|
||||
get_company_price_history_payload = {
|
||||
"operationName": "getCompanyPriceHistory",
|
||||
"variables": {
|
||||
"adjusted": True,
|
||||
"adjustmentType": "SO",
|
||||
"end": "2023-10-28",
|
||||
"start": "2023-10-01",
|
||||
"symbol": "BNS",
|
||||
"unadjusted": False,
|
||||
},
|
||||
"query": get_company_price_history_query,
|
||||
}
|
||||
|
||||
get_company_most_recent_trades_query = """query getCompanyMostRecentTrades(
|
||||
$symbol: String!
|
||||
$limit: Int
|
||||
) {
|
||||
trades: getCompanyMostRecentTrades(
|
||||
symbol: $symbol,
|
||||
limit: $limit
|
||||
) {
|
||||
price
|
||||
volume
|
||||
datetime
|
||||
sellerId
|
||||
sellerName
|
||||
buyerId
|
||||
buyerName
|
||||
exchangeCode
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
get_company_most_recent_trades_payload = {
|
||||
"operationName": "getCompanyMostRecentTrades",
|
||||
"variables": {
|
||||
"symbol": "BNS",
|
||||
"limit": 51,
|
||||
},
|
||||
"query": get_company_most_recent_trades_query,
|
||||
}
|
||||
|
||||
get_company_news_events_query = """query getNewsAndEvents(
|
||||
$symbol: String!,
|
||||
$page: Int!,
|
||||
$limit: Int!,
|
||||
$locale: String!
|
||||
) {
|
||||
news: getNewsForSymbol(
|
||||
symbol: $symbol,
|
||||
page: $page,
|
||||
limit: $limit,
|
||||
locale: $locale
|
||||
) {
|
||||
headline
|
||||
datetime
|
||||
source
|
||||
newsid
|
||||
summary
|
||||
}
|
||||
events: getUpComingEventsForSymbol(symbol: $symbol, locale: $locale) {
|
||||
title
|
||||
date
|
||||
status
|
||||
type
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
get_company_news_events_payload = {
|
||||
"operationName": "getNewsAndEvents",
|
||||
"variables": {"symbol": "ART", "page": 1, "limit": 100, "locale": "en"},
|
||||
"query": get_company_news_events_query,
|
||||
}
|
||||
|
||||
get_company_filings_query = """query getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit: Int) {
|
||||
filings: getCompanyFilings(
|
||||
symbol: $symbol
|
||||
fromDate: $fromDate
|
||||
toDate: $toDate
|
||||
limit: $limit
|
||||
) {
|
||||
size
|
||||
filingDate
|
||||
description
|
||||
name
|
||||
urlToPdf
|
||||
}
|
||||
}"""
|
||||
|
||||
get_company_filings_payload = {
|
||||
"operationName": "getCompanyFilings",
|
||||
"variables": {
|
||||
"symbol": "AC",
|
||||
"fromDate": "2020-09-01",
|
||||
"toDate": "2023-09-20",
|
||||
"limit": 100,
|
||||
},
|
||||
"query": get_company_filings_query,
|
||||
}
|
||||
|
||||
historical_dividends_query = """query getDividendsForSymbol(
|
||||
$symbol: String!
|
||||
$page: Int,
|
||||
$batch: Int
|
||||
) {
|
||||
dividends: getDividendsForSymbol(
|
||||
symbol: $symbol
|
||||
page: $page
|
||||
batch: $batch
|
||||
) {
|
||||
pageNumber
|
||||
hasNextPage
|
||||
dividends
|
||||
{
|
||||
exDate
|
||||
amount
|
||||
currency
|
||||
payableDate
|
||||
declarationDate
|
||||
recordDate
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
historical_dividends_payload = {
|
||||
"operationName": "getDividendsForSymbol",
|
||||
"variables": {
|
||||
"batch": 10,
|
||||
"page": 1,
|
||||
"symbol": "BNS",
|
||||
},
|
||||
"query": historical_dividends_query,
|
||||
}
|
||||
|
||||
get_company_analysts_query = """query getCompanyAnalysts(
|
||||
$symbol: String!
|
||||
$dataType: String,
|
||||
) {
|
||||
analysts: getCompanyAnalysts(
|
||||
datatype: $dataType,
|
||||
symbol: $symbol
|
||||
) {
|
||||
totalAnalysts
|
||||
priceTarget
|
||||
{
|
||||
highPriceTarget
|
||||
lowPriceTarget
|
||||
priceTarget
|
||||
priceTargetUpside
|
||||
}
|
||||
consensusAnalysts
|
||||
{
|
||||
consensus
|
||||
buy
|
||||
sell
|
||||
hold
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
get_company_analysts_payload = {
|
||||
"operationName": "getCompanyAnalysts",
|
||||
"variables": {
|
||||
"symbol": "BNS",
|
||||
"datatype": "equity",
|
||||
},
|
||||
"query": get_company_analysts_query,
|
||||
}
|
||||
|
||||
get_earnings_date_query = """query getEnhancedEarningsForDate(
|
||||
$date: String!
|
||||
) {
|
||||
getEnhancedEarningsForDate(
|
||||
date: $date
|
||||
) {
|
||||
symbol
|
||||
companyName
|
||||
announceTime
|
||||
estimatedEps
|
||||
actualEps
|
||||
epsSurprisePercent
|
||||
epsSurpriseDollar
|
||||
}
|
||||
}"""
|
||||
|
||||
get_earnings_date_payload = {
|
||||
"operationName": "getEnhancedEarningsForDate",
|
||||
"variables": {
|
||||
"date": "2023-10-04",
|
||||
},
|
||||
"query": get_earnings_date_query,
|
||||
}
|
||||
|
||||
get_index_overview_query = """query getIndexBySymbol(
|
||||
$symbol: String!,
|
||||
$locale: String
|
||||
) {
|
||||
getIndexBySymbol(
|
||||
symbol: $symbol,
|
||||
locale: $locale
|
||||
) {
|
||||
name
|
||||
intro
|
||||
overview
|
||||
}
|
||||
}"""
|
||||
|
||||
get_index_overview_payload = {
|
||||
"operationName": "getIndexBySymbol",
|
||||
"variables": {
|
||||
"symbol": "^TSX",
|
||||
},
|
||||
"query": get_index_overview_query,
|
||||
}
|
||||
|
||||
get_index_constituents_query = """query getIndexConstituents(
|
||||
$symbol: String!
|
||||
) {
|
||||
constituents: getIndexConstituents(
|
||||
symbol: $symbol
|
||||
) {
|
||||
symbol
|
||||
quotedMarketValue
|
||||
longName
|
||||
shortName
|
||||
weight
|
||||
exShortName
|
||||
exchange
|
||||
exLongName
|
||||
}
|
||||
keyData: getIndexKeyData(
|
||||
symbol: $symbol
|
||||
) {
|
||||
adjMarketCap
|
||||
avgConstituentMarketCap
|
||||
numConstituents
|
||||
top10HoldingsAdjMarketCap
|
||||
ytdPriceReturn
|
||||
prevDayPriceReturn
|
||||
prevMonthPriceReturn
|
||||
prevQuarterPriceReturn
|
||||
percentWeightLargestConstituent
|
||||
peRatio
|
||||
pbRatio
|
||||
priceToSales
|
||||
divYield
|
||||
pcfRatio
|
||||
}
|
||||
}"""
|
||||
|
||||
get_index_constituents_payload = {
|
||||
"operationName": "getIndexConstituents",
|
||||
"variables": {
|
||||
"symbol": "^TSX",
|
||||
},
|
||||
"query": get_index_constituents_query,
|
||||
}
|
||||
|
||||
get_stock_list_query = """query getStockListSymbolsWithQuote(
|
||||
$stockListId: String!
|
||||
$locale: String,
|
||||
) {
|
||||
stockList: getStockListSymbolsWithQuote(
|
||||
stockListId: $stockListId,
|
||||
locale: $locale
|
||||
) {
|
||||
stockListId
|
||||
name
|
||||
description
|
||||
longDescription
|
||||
metricTitle
|
||||
listItems
|
||||
{
|
||||
symbol
|
||||
longName
|
||||
rank
|
||||
metric
|
||||
price
|
||||
priceChange
|
||||
percentChange
|
||||
volume
|
||||
}
|
||||
totalPriceChange
|
||||
totalPercentChange
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}"""
|
||||
|
||||
get_stock_list_payload = {
|
||||
"operationName": "getStockListSymbolsWithQuote",
|
||||
"variables": {"locale": "en", "stockListId": "TOP_VOLUME"},
|
||||
"query": get_stock_list_query,
|
||||
}
|
||||
|
||||
get_company_insiders_query = """query getCompanyInsidersActivities(
|
||||
$symbol: String
|
||||
) {
|
||||
getCompanyInsidersActivities(
|
||||
symbol: $symbol
|
||||
) {
|
||||
insiderActivities {
|
||||
periodkey
|
||||
buy {
|
||||
name
|
||||
trades
|
||||
shares
|
||||
sharesHeld
|
||||
tradeValue
|
||||
}
|
||||
sell {
|
||||
name
|
||||
trades
|
||||
shares
|
||||
sharesHeld
|
||||
tradeValue
|
||||
}
|
||||
}
|
||||
activitySummary {
|
||||
periodkey
|
||||
buyShares
|
||||
soldShares
|
||||
netActivity
|
||||
totalShares
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
get_company_insiders_payload = {
|
||||
"operationName": "getCompanyInsidersActivities",
|
||||
"variables": {
|
||||
"symbol": "CNQ",
|
||||
},
|
||||
"query": get_company_insiders_query,
|
||||
}
|
||||
|
||||
|
||||
get_quote_for_symbols_query = """query getQuoteForSymbols($symbols: [String]) {
|
||||
getQuoteForSymbols(symbols: $symbols) {
|
||||
symbol
|
||||
longname
|
||||
price
|
||||
prevClose
|
||||
priceChange
|
||||
percentChange
|
||||
weeks52high
|
||||
weeks52low
|
||||
}
|
||||
}"""
|
||||
|
||||
get_quote_for_symbols_payload = {
|
||||
"operationName": "getQuoteForSymbols",
|
||||
"variables": {
|
||||
"symbols": [
|
||||
"SRE:US",
|
||||
"BWVTF:US",
|
||||
"C.P.K:US",
|
||||
"BAC.PY:US",
|
||||
"BALTF:US",
|
||||
"BACRP:US",
|
||||
],
|
||||
},
|
||||
"query": get_quote_for_symbols_query,
|
||||
}
|
||||
|
||||
get_index_price_history_query = """query getIndexPriceHistory($symbol: String!, $start: String, $end: String, $adjusted: Boolean, $adjustmentType: String, $unadjusted: Boolean, $limit: Int) {\n getIndexPriceHistory(\n symbol: $symbol\n start: $start\n end: $end\n adjusted: $adjusted\n adjustmentType: $adjustmentType\n unadjusted: $unadjusted\n limit: $limit\n ) {\n datetime\n openPrice\n closePrice\n high\n low\n volume\n change\n changePercent\n triv\n }\n}"}"""
|
||||
|
||||
get_index_price_history_payload = {
|
||||
"operationName": "getIndexPriceHistory",
|
||||
"variables": {
|
||||
"symbol": "^TSX",
|
||||
"start": "2023-12-01",
|
||||
"end": "2023-12-31",
|
||||
"adjusted": True,
|
||||
"adjustmentType": "SO",
|
||||
"unadjusted": False,
|
||||
},
|
||||
"query": get_index_price_history_query,
|
||||
}
|
||||
1075
openbb_platform/providers/tmx/openbb_tmx/utils/helpers.py
Normal file
1075
openbb_platform/providers/tmx/openbb_tmx/utils/helpers.py
Normal file
File diff suppressed because it is too large
Load Diff
1549
openbb_platform/providers/tmx/poetry.lock
generated
vendored
Normal file
1549
openbb_platform/providers/tmx/poetry.lock
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
openbb_platform/providers/tmx/pyproject.toml
vendored
Normal file
22
openbb_platform/providers/tmx/pyproject.toml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
[tool.poetry]
|
||||
name = "openbb-tmx"
|
||||
version = "1.0.0"
|
||||
description = "Unofficial TMX data provider extension for the OpenBB Platform - Public Canadian markets data for Python and Fast API."
|
||||
authors = ["OpenBB <hello@openbb.co>"]
|
||||
readme = "README.md"
|
||||
packages = [{ include = "openbb_tmx" }]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
aiohttp-client-cache = "^0.10.0"
|
||||
aiosqlite = "^0.19.0"
|
||||
random-user-agent = "^1.0.1"
|
||||
exchange-calendars = "^4.2.8"
|
||||
openbb-core = "^1.1.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.plugins."openbb_provider_extension"]
|
||||
tmx = "openbb_tmx:tmx_provider"
|
||||
1
openbb_platform/providers/tmx/tests/__init__.py
Normal file
1
openbb_platform/providers/tmx/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
56418
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_bond_prices_fetcher.yaml
vendored
Normal file
56418
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_bond_prices_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,795 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-08-25", "toDate": "2023-08-31", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-09-22", "toDate": "2023-09-28", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-08-18", "toDate": "2023-08-24", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-07-21", "toDate": "2023-07-27", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-07-28", "toDate": "2023-08-03", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[{"size":"95.39 KB","filingDate":"2023-08-03","description":"Continuous
|
||||
disclosure","name":"52-109F2 - Certification of interim filings - CFO (E)","urlToPdf":"http://www.investorx.ca/Doc/2308150646014439"},{"size":"97
|
||||
KB","filingDate":"2023-08-03","description":"Continuous disclosure","name":"52-109F2
|
||||
- Certification of interim filings - CEO (E)","urlToPdf":"http://www.investorx.ca/Doc/2308150645586460"},{"size":"472.49
|
||||
KB","filingDate":"2023-08-03","description":"Continuous disclosure","name":"Interim
|
||||
financial statements/report - English","urlToPdf":"http://www.investorx.ca/Doc/2308150645555689"},{"size":"400.83
|
||||
KB","filingDate":"2023-08-03","description":"Continuous disclosure","name":"A
|
||||
- English","urlToPdf":"http://www.investorx.ca/Doc/2308150645105523"},{"size":"308.45
|
||||
KB","filingDate":"2023-08-02","description":"Continuous disclosure","name":"News
|
||||
release - English","urlToPdf":"http://www.investorx.ca/Doc/2308150617461485"}]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '967'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"3c7-ZPQpXSIQW2OoMN8sO4MUkGsZsw4"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-07-07", "toDate": "2023-07-13", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-09-08", "toDate": "2023-09-14", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-06-30", "toDate": "2023-07-06", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-09-01", "toDate": "2023-09-07", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-09-29", "toDate": "2023-09-30", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-08-11", "toDate": "2023-08-17", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-07-14", "toDate": "2023-07-20", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-09-15", "toDate": "2023-09-21", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyFilings", "variables": {"symbol": "SHOP",
|
||||
"fromDate": "2023-08-04", "toDate": "2023-08-10", "limit": 1000}, "query": "query
|
||||
getCompanyFilings($symbol: String!, $fromDate: String, $toDate: String, $limit:
|
||||
Int) {\n filings: getCompanyFilings(\n symbol: $symbol\n fromDate: $fromDate\n toDate:
|
||||
$toDate\n limit: $limit\n ) {\nsize\nfilingDate\ndescription\nname\nurlToPdf\n}\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"filings":[]}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '24'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:25 GMT
|
||||
Etag:
|
||||
- W/"18-YqxSvZ09xIbNEA8V1atJvD3G14s"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,73 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"operationName": "getNewsAndEvents", "variables": {"symbol": "SHOP", "page":
|
||||
1, "limit": 5, "locale": "en"}, "query": "query getNewsAndEvents(\n $symbol:
|
||||
String!,\n $page: Int!,\n $limit: Int!,\n $locale: String!\n) {\n news:
|
||||
getNewsForSymbol(\n symbol: $symbol,\n page: $page,\n limit: $limit,\n locale:
|
||||
$locale\n ) {\n headline\n datetime\n source\n newsid\n summary\n }\n events:
|
||||
getUpComingEventsForSymbol(symbol: $symbol, locale: $locale) {\n title\n date\n status\n type\n }\n }\n"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA62VbU/iQBDH39+nmOs9RJO22bYUWnyFPCi5E5DiUy6+WOkiG8su2W7Bxvjdb1bE
|
||||
06jcqZc0LW1m/vPf38wuN1ZKNbXqN5Zgy9yq/7qxpoymGRfMqlvJVM75pIQDpsZTKnQOLcUXDIZs
|
||||
LFXq7PPLKXyN3QB2eZZxKYAL2M3o+Ao6iqe0dJrlBVNwIAW+QEIzllu2qcg0n5kCPvEDx/McPxqR
|
||||
qE4IXg4J8Y5huSzU2AT10NmEZwwWnMJhITU7YCmnGGI889SqV2rEj0lEopAEYRBFmFzMZlSVmN0V
|
||||
minBtA3tBVPlcsoUc5ytB9GmVHMXHOjJBZsZt35kg/G1jR/XALpi7MJW7yxp2zBKTuuQ7PcH2zZQ
|
||||
mCvXda1b+wm30ZSV0OIpdDU0LikXn+E5yl3F6NUTXPdcYck1cq243mOum+iFhl4QvJNejdTimh95
|
||||
Vc8L4ijyPkYv/EPvL/heo3fMlGbXMKBKC6byFY61lJaQaCQ3M8Gwl8kLmsGIXqOT2TzjVOCiX4RE
|
||||
qhtGzOgws6glVxtIhaHnk2rskbDi4xU8JvWj29uDfgcGw6Mk6TZs9O/ahosLpLpiAlt7P/u7bei1
|
||||
T5KT7rCNjBASrNZrrygBcmokrcZh/bg9PN2Gre9frn3iNXeOXyC1htIQQha48hxGU44b87BAdqYZ
|
||||
pmiHC6TCEdOQ5UWmXxki4o9Ibc2n8uYhCgOv4tWq1TBAPb/2ZIiGbMFEweBojuPxDahIYU/JPIeB
|
||||
khOuzfeg+g3OGFVOH8fMMb92EAXuC4YzRvMpdDK5hIHMuTYH0EQq6KA5HIymFPl/Q2PUJjjimIBl
|
||||
swxaCOrO8NF8LGdcXGKXFizXWL9Fy5dIEsf7IMm4FnmkEofVgAReGL9nO/bHWprd6PlvO8vkHclz
|
||||
2zId06v/A811Zpw/EMDmCHzmyGXFdoax9yisuiiyDC1rqot8/abLuVFYJ9716plsyhc8ZYh6TktZ
|
||||
/JPgOuUVRWwTnq05HgsPQ79R7j7y9vz29tNvghRykxkHAAA=
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:26 GMT
|
||||
Etag:
|
||||
- W/"719-/eLbzQXgYik/dQCOz8PB4QsD1w4"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,147 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"operationName": "getQuoteBySymbol", "variables": {"locale": "en", "symbol":
|
||||
"RY"}, "query": " query getQuoteBySymbol(\n $symbol: String,\n $locale: String\n)
|
||||
{\n getQuoteBySymbol(symbol: $symbol, locale: $locale) {\n symbol\n name\n price\n priceChange\n percentChange\n exchangeName\n exShortName\n exchangeCode\n sector\n industry\n volume\n openPrice\n dayHigh\n dayLow\n MarketCap\n MarketCapAllClasses\n peRatio\n prevClose\n dividendFrequency\n dividendYield\n dividendAmount\n dividendCurrency\n beta\n eps\n exDividendDate\n shortDescription\n longDescription\n website\n email\n phoneNumber\n fullAddress\n employees\n shareOutStanding\n totalDebtToEquity\n totalSharesOutStanding\n sharesESCROW\n vwap\n dividendPayDate\n weeks52high\n weeks52low\n alpha\n averageVolume10D\n averageVolume30D\n averageVolume50D\n priceToBook\n priceToCashFlow\n returnOnEquity\n returnOnAssets\n day21MovingAvg\n day50MovingAvg\n day200MovingAvg\n dividend3Years\n dividend5Years\n datatype\n issueType\n qmdescription\n }\n}\n"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/RY
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA61Uy27jNhTd9ysIbbqorBEp6uWdIydogc7k4XTaLGmJsYhIpEJS9giD/HsvaSVK
|
||||
AnRXwwvp3Ad5j849P4OGWRasfwYHbm9HZfnFtJv6veocZuan4O4hCAPJeu6e1cQ6dMHkE1KPqGKS
|
||||
NQyigxY1hHFCI5LMr1XL5AHAOEozgLiuubQLSHFRUhIG/EftsW/nA+6VVtIqtLOqfkKXczBwebtW
|
||||
afuatvsnWGor1Syg4bVVGl6vhGSydrVCNqOxegLQXV3IA4BH1Y2uF8mygmIaBmrg8uZ1kCTK0xD4
|
||||
mX4Xh/Y8WVZ64E91mhPC4CvTT9xWbACkKEhS4iLPc/IusOm6qmPGcONT8jLLcpLQ1DFyx6xQAOMo
|
||||
96TxY9UpMx+fQftGHEXDZXOl+fPIZe0GuB2Ztlx3U7DEHwTvmmBNI5ws4KZXo7TQLEqKBa1GredO
|
||||
1WYLPfbcSSCOCppSAhPywd0Uvk9Rwi93JG/n2i2zjmYSE7qK8YpQFMdr/49iR7z7Pltuai0GGEz+
|
||||
h1yQMEhJ7gDbcmRPCnVMH7ixaA+JBgk5Z0bQtFPy8L/2RH9Yl80QMMK1EY+CN+jRS0VAX8P1ESRg
|
||||
UK36gckphJ6PXINkEEjYKAk5TDYu3IOkXcn+LKkQnTjrbLvq4aAD70Htb91CuIAZtZNjCKV6UBq4
|
||||
XCp9RzYIC+16Lx3zVhuh+/ac6u5dK+n2yJU3y1RwtrAtYk0jHEvQBcSsnbykn92R8le0i/xBCt40
|
||||
NAJ1aAH9geUT3xvhP25r7WDWX76cTqdI7+sI5nSL1jPhrMCtegtEfxv7PXc79htGFGeozOmqSMoU
|
||||
4o9j122aRnMDOgowuulYzdF30XV8BVsheFi9zb/jtQb56elXg7Z8AGE71kL0FSxAA5khuq1CVG38
|
||||
DYZOTdytUVAUKcVecEzz69HuLIzllnqNaUySLIkx6NYqYHPL9/ZeXT6Pwk5O5bicAztXaz4XZynO
|
||||
CMFza3O5q+6u/w7WEmYCwzj5PXceR7OYZMtS3bDpw26QFUk8q/zJpKQ9OwiF04s3sPMuEudRCW7B
|
||||
uqH1SxjHSVqAFTGQJmjou7coHG+DdUpoipPsUyhxIRrHaZl+DqU+lGGaOG/zjnyvLpR6co6QY/yG
|
||||
Vcy0V/46qyQCX4IvMmp5LV9JwzSK6QJvwMuscZfNM++HBH9VRyBwc3QckgyGoHMojd+HcBnRrIzP
|
||||
LkriDzFCIhqnRbpwmjxwpuGcPHJ++oqmMwqeWfpGltlpcMzz83XB7I0Z+f0ZrHYAPPfNBwPZvlt8
|
||||
ZyMmeHl5+eVfcjLaKQ0HAAA=
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:23 GMT
|
||||
Etag:
|
||||
- W/"70d-fHugecv56LrRY9XFNFeamlQimkE"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"operationName": "getQuoteBySymbol", "variables": {"locale": "en", "symbol":
|
||||
"NTR"}, "query": " query getQuoteBySymbol(\n $symbol: String,\n $locale: String\n)
|
||||
{\n getQuoteBySymbol(symbol: $symbol, locale: $locale) {\n symbol\n name\n price\n priceChange\n percentChange\n exchangeName\n exShortName\n exchangeCode\n sector\n industry\n volume\n openPrice\n dayHigh\n dayLow\n MarketCap\n MarketCapAllClasses\n peRatio\n prevClose\n dividendFrequency\n dividendYield\n dividendAmount\n dividendCurrency\n beta\n eps\n exDividendDate\n shortDescription\n longDescription\n website\n email\n phoneNumber\n fullAddress\n employees\n shareOutStanding\n totalDebtToEquity\n totalSharesOutStanding\n sharesESCROW\n vwap\n dividendPayDate\n weeks52high\n weeks52low\n alpha\n averageVolume10D\n averageVolume30D\n averageVolume50D\n priceToBook\n priceToCashFlow\n returnOnEquity\n returnOnAssets\n day21MovingAvg\n day50MovingAvg\n day200MovingAvg\n dividend3Years\n dividend5Years\n datatype\n issueType\n qmdescription\n }\n}\n"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/NTR
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA61VS4/bNhC+91cQAooeaiui3vapjr1BgWZ3k5XTNkdampWIpUiFpOyqwf73DiX5
|
||||
se21hg/mN5yPM988/N2rmGXe+rtXg/3cKwvvh2JoD0o4zMy/vIf9k7fwJGvBHXqrOUjy0VY+op3m
|
||||
JcJZ4q/S+bRtmKwRC/wwQwh0CdJewShJM5ovPPirHLGHiXevtJJWkcKq8oXczUbP3Ssape35WvGn
|
||||
d/XdquoKGiit0ni8ZxY0Z8IgyGXVG6sHhDc1RtcL22tHe1Sid4xZuMqDYOGpDuSnOZl4TKZiw6+8
|
||||
bvCc+nE8nj+q08V8z/QL2C3rvHWUJWmS02QVZzf4RoitYMaAmW7EaUpzSp0kT8xy5a1p6OfurOG4
|
||||
FcpMOqYrfIofeQWy+qDhWw+ydOF/7pnGvMTgXe1fOYgK2f08S67oplW9tE7rJLqi217rmepLsUOS
|
||||
A7jaB/4qCsMIH4UOA8U6hiv85KFTeTf77lBR9AuDMFrScBnmJAjW49cPnPKuQDswpeYdJiZv2oQb
|
||||
YhsgJ6VF9ZMhgukajCXPoC0X/G/QpNOq6kv8cRhIyTpWcju4zhJK1v8rJzn7z9aJxTYagLSMS1Jq
|
||||
1RE5XbJGcqtVDXJBOmWZaRaEyYp0jTJdg3IwYRvV1w3h1kzuz6rsjQvufP/UgAa0nwOuhTowQQSw
|
||||
CkNDDy6NZUJAdQmSnLhtiHbEYiBh8CMxDdPgkz36l6rtmBwcHTa3GjnPybNLc+MLGivLxfSGu/QF
|
||||
c8FHCotxmwUxIASX9Y1gCI7Jlw20vERydwkqM+VsQB+506viGmcMA8M5fWa6JZiwVS36Oxkvahww
|
||||
lJclei5b7AumCV7S6O64lMSngXSC2WelW+MqfYKD4WODNdZ2Zv3u3el08udC+Ji1m3iU2O0iLo/g
|
||||
6Mwvb+1YFwkPfXsAtwF+piQKUrKKomWeBK5Fn3shNlWFYRjXyJQSukKlC4vVt+SOGbsoegyC0Ay3
|
||||
ASmYeWFWKax+8duCbDdjCJ1QA8BIEE20Y3Uee4vSygo19dbxKk7iLIpxpVjsA7GDg92ru29IPrhp
|
||||
S2e8cJ7mP65RSON05jV3xfbp8Q9vLTF43Fgnt2twGQXBiqbBzWh/YsN1QuNlQHFIR13hxSRhMy4x
|
||||
SiM/Ci+gGDcZhpPj4mACW9oFFwTUrTZ2BM1q+H1ckTTYeeswSWnsdsIbU+RMNA7iVZT9y5SMpjAN
|
||||
siSb/xf26r1SL4j6mOgF2+KofBijWWIk2Lm9lo/yrFfu9DqDG9ylFtWPfZqM6zik9+qI0m2O9biW
|
||||
wyhN02lTJ8GtKfVpOrsEt4Y88gMs11XJ6CswPa7BlF7RZEaXgZ+PNDhIQ+f0hilO/J8xpof9BG4L
|
||||
BL611Zvltbkd0KLvOsGxk15fX3/4BzRtXseGBwAA
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:23 GMT
|
||||
Etag:
|
||||
- W/"786-J6jDjEdFePspLg4uw0/rmblQy0Q"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,75 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"operationName": "getQuoteBySymbol", "variables": {"locale": "en", "symbol":
|
||||
"SHOP"}, "query": " query getQuoteBySymbol(\n $symbol: String,\n $locale:
|
||||
String\n) {\n getQuoteBySymbol(symbol: $symbol, locale: $locale) {\n symbol\n name\n price\n priceChange\n percentChange\n exchangeName\n exShortName\n exchangeCode\n sector\n industry\n volume\n openPrice\n dayHigh\n dayLow\n MarketCap\n MarketCapAllClasses\n peRatio\n prevClose\n dividendFrequency\n dividendYield\n dividendAmount\n dividendCurrency\n beta\n eps\n exDividendDate\n shortDescription\n longDescription\n website\n email\n phoneNumber\n fullAddress\n employees\n shareOutStanding\n totalDebtToEquity\n totalSharesOutStanding\n sharesESCROW\n vwap\n dividendPayDate\n weeks52high\n weeks52low\n alpha\n averageVolume10D\n averageVolume30D\n averageVolume50D\n priceToBook\n priceToCashFlow\n returnOnEquity\n returnOnAssets\n day21MovingAvg\n day50MovingAvg\n day200MovingAvg\n dividend3Years\n dividend5Years\n datatype\n issueType\n qmdescription\n }\n}\n"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA7VVUW/bNhB+368gDAxdMVmVZEmW8zRXSdYCTZxFRrc+0tLZIkyRDElZVYv89x0l
|
||||
23Ez9HF5cfjdHe/u492n75OKWjq5+j7Zgf2rlRbe90XfbCR3mDn+Nyk+rB4m3kTQBtyplopte/JR
|
||||
lD7JOTWGLEnRbqSumKAWyGdpmdiRoqYaDMYpzUoMDIOZH2fHY15TsXOgn4YIgS5B2BcwyYI4Tr0J
|
||||
fC0H7H5MvZZaCitJYWW5JzdH48T5YVXantyKfyYvsbmsXkADpZXaHaGsheRy1yPKRNUaq3vXndza
|
||||
DgtH9CB56+6LgiyNktibSAXi4dyMh+T1H9iuHlvLogH4JDt3Dv0Fnu+o3oPNqUIoirNsMUvTZJFe
|
||||
GJacDxQiUVfhLMoWSRgkiYtV8Egtk1jSQCEcci4NjHdnc8zFDqwCUd1qeGpBlFi8aDl/MXxhwKvX
|
||||
4LKRrbCv0bzV+vKKDbipiPwgSechNgoKy5uGfhQG7s8hX6+Psdf45GORxj3BNZhSM4WVi4tZkdst
|
||||
aEOoIDAtZdO49yaKU7uVuiE4EQ3VjPcE39Y0lHP0rEjDKsO+Adm0hglwHPmYhUux+3+TkHUNZMsw
|
||||
pqaG2A7dYdfgfJorYtrNOTUxOCDuH0N+i2e/Yn6MMiXlJArCjOCTgWjh7ZgGi8FptJcxyfynMWMN
|
||||
P0l2rIZgC7Iz5NT+KYVxDZYSR7q0l0zgHZQckAKwjqszM8YjTJS8rdzSWkyLEYqK/o0hHWwMs+AR
|
||||
VfeGuSoNbg9ghJJq2qrzcc+k2eOvkSVDLwHImt5jj7e0hI2U+7feQMOyod+k8Mndf+nAnSO0qqZY
|
||||
ptLSFW8IljdUdH5EW1NLtrRknFknNRftuevHPuBMyQPth3fzzkhRM6Ww0bGcE4qLiPdxN17HlnGs
|
||||
amuVuXr3rus634yOPmZzytJQ5nTx4+MfPxpULQXct80GnMT8HpI0nJEoDqdRFmVo3+J2LasKKcN9
|
||||
moRJSFZvcikEtllYDWC9PzXuZ0VuuZTaIytraUfx994j+XLIrLjsAYZwt4rD2iF1q9YWFlvC1pzW
|
||||
BClqzTzFRbUSG7uGjV3Lm6eWWdzxwA/jo2HU6FfBWRwlWeakerja3BT54+rvkzYcukHOUPGiJAmc
|
||||
10lFkO1RDEbHDmBvkqg+KmTqp9kZ5E4l49gPUAspVzV1VQUR6iwqHz2Apjv4PMhvGFyjJ7aTxcEr
|
||||
08yZZukMq0hemZLRtIijeXL84qzle5xDrCT1g3h2BnNq6tuhnHnio68G22qxEieypjP0j17wJUqE
|
||||
dWoYZX40fgOi8E4ekLvlwdEXIL1pHGWDKQkuTFnmJ8nREAWXlvnCj5PggsrZF6DajLJ6wpIjFnjD
|
||||
V9v2yk0pjHXiN8yYFtYjmBcIPDXVDzK5VIrjDg9icv7KPT8///IvYC+baQYIAAA=
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:26 GMT
|
||||
Etag:
|
||||
- W/"806-6HBMmFREzLBa0L/cA9O/rZwtqtA"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
1312
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_equity_search_fetcher.yaml
vendored
Normal file
1312
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_equity_search_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24247
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_countries_fetcher.yaml
vendored
Normal file
24247
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_countries_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24249
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_holdings_fetcher.yaml
vendored
Normal file
24249
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_holdings_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24249
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_info_fetcher.yaml
vendored
Normal file
24249
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_info_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24249
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_search_fetcher.yaml
vendored
Normal file
24249
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_search_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24249
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_sectors_fetcher.yaml
vendored
Normal file
24249
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_etf_sectors_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
109
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_gainers_fetcher.yaml
vendored
Normal file
109
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_gainers_fetcher.yaml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"operationName": "getStockListSymbolsWithQuote", "variables": {"locale":
|
||||
"en", "stockListId": "TOP_PRICE_PERFORMER"}, "query": "query getStockListSymbolsWithQuote(\n $stockListId:
|
||||
String!\n $locale: String,\n) {\n stockList: getStockListSymbolsWithQuote(\n stockListId:
|
||||
$stockListId,\n locale: $locale\n ) {\n stockListId\n name\n description\n longDescription\n metricTitle\n listItems\n {\n symbol\n longName\n rank\n metric\n price\n priceChange\n percentChange\n volume\n }\n totalPriceChange\n totalPercentChange\n createdAt\n updatedAt\n }\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA5WZ227jRhKG7/cpCF8sEmBM9PngO0mWD7uyxpbkmWQWi6BH6pG5I5EOSTlxgnn3
|
||||
VMunriY9zmaAGKJOH6ur/vqr9OfByrXu4OjPg6atll8nRdOiB+erg6ODxfvLXy5n56PxL5fj2cn7
|
||||
2cV4dvDuoHRbH56sbrPLulj67NLXX6p662t4cuWbZV3ctkVVwmvm4eOarL1xbXbj7nxW7drbx1ev
|
||||
4LLPtq7+6tsc3rmpyvUxevdi/lPmylUGfz9kTfpRG1evfdNmt3uGtSvKJvvh8322qjbwVHbnNjv/
|
||||
Y1aU+6+5dfBKTrKVu2/Cl219C29bFO0m3Ao8cezuH+9mdOPKtQ9AIQyt3zYHR/+ByNxvP1cbePFo
|
||||
fv1IO30IxKgqm9bDlwbsbF59aX9ztc/Oy2X4ptqVXw+O6NNXwuuVyYUxkkl4dg9/cMSZFTnVj48f
|
||||
CY6EylW45uulL9unqzQXXFMi3x3cVZtdIGBCKvrtXcR4Ov8ZM64r75r7bNKuXphYxMRZbhghRL8w
|
||||
UWlzyhOkQ/h20UE6JLmxysRMwmjOEdN4coKZxoeT7KQoXbks3CYbVfVtVT/EcFJsi9avnkl5RMp0
|
||||
HkeOEoiStQkmEw/XMCYDdskQpVCIcbQYJWfrSrcqXJktCjjS7zGKmJHnShBCI05mSMJIOnzkBUwa
|
||||
xHU2PcZcQ9+6y7rKpq7d1RC9U9dAwt35uvHZxMNft4YCO3bF5j4beiiH8eLkGVXGqDTn2lobHbwx
|
||||
uUpYec5YB5fnWlOu40Sk1AiJI/qvRRrRel39z7f7+oCIbrcQzFDlH1xduM8bn32o2qJcZ/MbqKLm
|
||||
GVrF0JBvkJ5SRXlAZa5SbJpL2VM/XBrOTRRsYSVD1MOL90m8gSGrvmQXVdnW3m2esXSERW2uDRRR
|
||||
jMWhYEwHi3XLmuRWSy4iLEoYYxqHc/hzmgjV/So7ravdbTb39R18S4OVx2BAwuGsTZSX4fxwibNc
|
||||
074SBz4Th80YTfBZn5+ep9K42RSQlIDU+rrcV07I1j3vPgHmu89VvQIVaF87eBvfgMkJg2y1UYQ1
|
||||
yWV68DJnpidfqRVS2+jgJaX4FhYn58ktwJWEHgs7ifFUbhTQxfpkKAQuwQNd78MzlBgWBZhTzRnm
|
||||
myW6PqvugegpO/dq5V7Y4q5DRR7Q4kKnXOQsVfjHaKbJKaixgkWlrpQRVGCFvxomCn81TKLFMBGo
|
||||
IuNRtIzKJU3zkfSVC1zllFIeIUFmK1wtH0dTDPQRbECQ8bL0y3CaSa3QuNFQnhMJAYuKhVrZzTWS
|
||||
0642kpwoKqJatlRIDDcdYLbpU369dppxh6E0Jxr0IS4EQrrBg0t9hxlyNIKTFg7X4lqezl7phM+c
|
||||
M+guv4FtAg2/deX9C2jcXygBWWZKiwhUqYd2koD2kXJOrYlQFSNU4N44ungF9HwLH1c8BfT9l32z
|
||||
CV/wQqowaZSJij/UaJKJousq4KpWXIYif6GUVCpsLebjxFrMPfRrcKUjd1u0HVlBfYVETpH+Xz4C
|
||||
28LR6ftUn9d+Wf1dJaZxL7G5VVhOpAZx7hyr4X0ONjgHGmEqrvGpzhdJ6c5bByq8TMJkEREEhxBk
|
||||
DmVO0/5G+kw1XBSU6RhJK8uxvn08OU3kJJzfSe0aX4NB3H6GP6MqT1x23CAMBC1x2SAzaZ6B4PXV
|
||||
AoPcV1GWUXhsEt8yvE6ybFhX1dcvhd+ssuGuKUrfNMjGjjYOrgyy8e/L/RftHdjbqcAouiswUEbq
|
||||
OEtz3pWibiaAqQBp4LEUCY2DPrp8pbwv3bL4Uiyzf7uyAfc7Ktr7jilnDGFSC8FnOD94x/8E7e6r
|
||||
ciKVZVGGWAYpglVzMZokLXBxnD0TT+BytvD1NjvxKx9s+7AC4xtbc8YxMEs6NoMGJDp2sitK4Hy5
|
||||
0lFzFCpxktMP48TpTHd3vkDJ8TfyIG5KUafkvY2yRwtYzo01Kh4iGKeSJ2IwScUTBm4QrrNqA3jr
|
||||
pI2zuAFp8OQ4iAJseleneJ8oaGG1iEuOSGmxpn66SAYcmB2y8a87ON2PvljftNnppvq870Iwm13A
|
||||
3LZpsjO/CvNZW2WjwTHAr/zvOA0UugMVpgrCI6kFH5yayu/PkzKdy4ajn5LCOocpcuN+h4FxUy2/
|
||||
ghwUZR+aRmiSk0TQbK+Fo70jhVREydjCIcartJgu3PKrL/8o/EtNnVYw7JZb+NiH8tpXVA+1QdSc
|
||||
p0IA7SyV4ZCaPfkahlyrosqCj0LYk9Q6TaCCtq6OS+sFzCIwGs2OSkD+dfKU9JpNTiyJ4mg4SUro
|
||||
ap5AzaB3uR1AtNkQ/r9KZzRUUZwgSgKDikbh4zlLdzChpfb1WcaFjoccyTVJWIfpGmZYtMsK0vFk
|
||||
B6f7w+LG//iChlZruaWoSjrTzXfNkiIGm7ar2XjxWgoe+zu/qW6hjC/2O8wmm3mI2xjC2vqeFOQM
|
||||
gxILhyZwM6JdK0C64g4XjQS/EuUgHhKv58lZ75rGb57EB58sR1Thc5WIqIR8MCOJV+8WBoUMgIkw
|
||||
gtJcanywZ8PTZH1xVtXFH2EOGxbr7Ni1Lvtndubq1eMGtRNDgWg1wbsABsreKeIey27BFVKCdi2M
|
||||
EBzE4SCZKyInNYBwtnDspVv7B+kBx/dspR5NyCsdk0t0Cwrm6NhD81x0jZPq22JBm7Q2GjEFPE68
|
||||
09l1kr1nuzYzex2KMkAhIBEpEOO5TStI9E3jjOaaWsaiVskkI8Im5TQ4e62arufZJCz0wzgU2mdw
|
||||
c8/nn/0AbfLwoWlGla8ROFdC86j6YQLrZEP/OMzBSMfqyRNBH05n41dzYeaLsgk6uvQ4DZCjfiMn
|
||||
DLoTFloKxVmhunsa0S1C8NMEhqAoKzRL1vDD6au30jcXvAFuETi1ispY0wDcdK2W7rawsJSVNt7m
|
||||
WEpgrEbog+NxAh+uLGYDLGmCYKiw3jYRFGfdn1rAoPRuvcI74zWOsRzPqYNxIhODdVmAMx27NRz7
|
||||
RRi4uj8VxC1L5lbC9BtNrVr0zdE9G0M4bRhZTSRjwlhpWTLzJyvDkdvsjdN+B4tGVcEwF6WMRZ4e
|
||||
5vvu70HkoWf1LOcEMVEzgBLDh/nT1RXmKh5yK5sO5seDqwxG+bcEQHDEq3gy/TOTy64rMX0HrTRn
|
||||
8UoMmqBJNsPXPaPddT7Po7FuUXsHWnDfneuEQKhyX+HxFsB0FxW9P7UF3ecmFlpqkh325CKpkslu
|
||||
C3kYbd1fsOR3saCfdg1oz48D0GRhTIrGeCGV0gjqIpk2wIvAm+F0+0yxUBiLcqpMNP6AMyY9y7me
|
||||
NnlIc2jsNF7sGGmTVj8aJavYx7VY2BfuoJrdy6r4b4zEQifweNSQtju60b6VFAsWURBkUgyReOHw
|
||||
KTVUr02fpzAp9xgqYRCsCIMmj7RS694FWjcFIC+sgn/xEhQ3/09pwQfUqWtW7td9sT82/cfRePHa
|
||||
aCwsIqZhRa9wJelu0fdUUhj5qY5/DmJGEDwlf7o+eyO84FvOwPa3N2BcwLK+NdhLguj3/tXEa+cu
|
||||
OukLNqFC4BU+ld/+++6grcDiX6LcouTxN7On/55e1RGW8DvGEiQMetWgPQg/szJ+SNkh0wvKjwiB
|
||||
zgpHzz4B7u529fbLvn379o+/AIiEcMhoIgAA
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:31 GMT
|
||||
Etag:
|
||||
- W/"2268-e3rt4kQv8I6aOB/LKVc+TM3z6Dk"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-WebKit-CSP:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,105 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"operationName": "getDividendsForSymbol", "variables": {"batch": 500,
|
||||
"page": 1, "symbol": "TD"}, "query": "query getDividendsForSymbol(\n $symbol:
|
||||
String!\n $page: Int,\n $batch: Int\n) {\n dividends: getDividendsForSymbol(\n symbol:
|
||||
$symbol\n page: $page\n batch: $batch\n ) {\n pageNumber\n hasNextPage\n dividends\n {\n exDate\n amount\n currency\n payableDate\n declarationDate\n recordDate\n }\n }\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/TD
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA52cQW/cNhCF7/0Ze/YGQ1ISKd+K5hz0XvSwibdtAMcJHLtIEPi/V+JwVe/Om8ks
|
||||
gVy6CvW+cp6kmSe1P3Z3h6fD7vbH7u7jvx/vjg93X9d/+HL4+/ju+dP74+Pu9uH5/v5m98/h67vj
|
||||
t6fflwO7278O91+PN6+X/PFjd/z29vC0HNxFisOewp7m3c3u8Onz88PT7ja8oXiz+/D8+Hh8+PB9
|
||||
+Vu//fp2Ofzl8P3w/v54sTKF5dDd8cP94fHw9PHzw/+H0z4sh2k5/Hj88Pnx7mJhoN3LzTnKsoL2
|
||||
NL5CoTfz5EGpKy0UKvs4ABSWnAAK5eVPF8qy0kQZ93GEKMtCuCs09O7KsrJWQENZ/kSMMii7ErpR
|
||||
LK/EfYjL38AoAaHEVrjXKGX2oETbK7F6BRWIJTNAkV7xopheidUrE0RZJQtCGbpRLK/E6pWEUQYF
|
||||
JXSjWF4J1SvItlG5r4RWuNco2YUSbK+E6hVUIJaUuxJa4XpQTK+E6pUMUVbJGaEM3SiWV5bDEV9B
|
||||
LAlRhFfcKJZXqHoF2ZYlZYGoFe56FLK9QtUrqEAsKXeFWuF6UEyvUPVKgSj4GUTdXiHbK1S9gncF
|
||||
e4VEv7KgDE4Uwythrl5BtiV8X1lXUBcKr7RQVq/MEoUXQhTgFSeK5ZX18OIVcAXxQowydKMYXlkP
|
||||
R2hbXohRhFem7ESxvFLW3lYpEPZKAV7xoRTbK2X1CmqzeSFEkV7xopheKdUroM3mhRhFeMWNYnml
|
||||
1H4FNJS8EKNIrzhJLKtkbQzihYAkgzHIRZJtp+TqFLQnGU9B64p82WQ7SUyjZG0I4oVwT3gi6SGx
|
||||
fJK1GYgXYpJwSTKOThTLKJM2A/FCgDIBo/hQJtspkzYDNUkxA60rhFO8KKZVJm0GapKig1t/F1Zx
|
||||
o1hembS+tklCFOmV4ERpDxiIMmp9bZOUKGMtXOxAqSv1XRm1vrZJyrvKCOZlL4rplVHra5ukaCbX
|
||||
34feXTG9MlavINuObUQHKOESZXA9Ckf7vjJUr6Cn8oizlXUF8Z3oWpTBvq8M1SuoQCyZAIr0ihfF
|
||||
9MpQvYJu/IPilaEWrm9XTK8M2gzUJOGuCK8U1+PQzmxD0magJim9koBXiutua2e2IakzUFK8wpnt
|
||||
+eOwuC5mO7MNSZ2BkuKVBLziRrG8ktQZKCleScAr2WVbO7MNnNmiWxxLDgIlAq/4UOzMNkR1BoqK
|
||||
VzhATeconrcewc5sQ1RnoNjsCVDEM8iNYnklqjMQS6ICrYU735WpOFEsrwS1t43tlcIlSmiFux7F
|
||||
zmzDOuRhlNDaaYmyFm44R3Hdbe3MNgS1tw2tnQYo4r7iRrG8ErR8v0nCXQliV1y3ODuzDaTl+01S
|
||||
7gqB+4oPxc5sa2KDUUjxCme2sQvF9App+X6TRLsCvOJFsbxC1SvoFkf4vWGgbq/YmS3N6hxE2Cs0
|
||||
93qF7MyWZm0OapLCKzT3eoXszJZmbQ5qkmhXOr1CdmZLszYzN0nxZKYZ9CtuFMsrRZuDmqTcldLt
|
||||
FTuzpaLNQU1S7kpBM7PnBQzZmS0V1SsF97ZUgFfcKJZXitbbNkm4K8Iro6ehJDu0pazl+01StE6U
|
||||
gVd8KHZqWzMuOHw0SbkrGfS2Y/KhmF7JWm/bJOUtLqN8xYtieSVXr4Anc5NEBZK97eBpKMnObYlz
|
||||
W2TbrHhl2tcHVweKndvSpH3n1CQhynLOi13xvKwjO7elSYv4m6T0ylgRL1A8cxCN9q6MWprdJCHK
|
||||
tO7WGYqPxNyUUWv4m6J8BI1rrxXGLhLr+uF8EjmFFUU+STVGvNiT5Bk9yM4n1xMHjMKSclOGZubr
|
||||
Uex8kgbte54mCVGmy/okV4HsfJIGrd9vkqhAa+GmPhTLK4OWZTdJ8bKBUivcGYrrUrbzSarflEIU
|
||||
lkQoXLjrUex8kpLaw7EkRKmFe40SXbd9O5+kpGXZTVL2cKkVrgvF8krSsmyWjOIlJtUYsXdXLK/E
|
||||
6hWEwpJyV2ItXA+KnU8Sf1OKCsSSEGXqK5CdT1LNJwO6gqokLFC9uPpQLK/E6hWQxDVJ2a2EdpF3
|
||||
oVheCatXAmoRqiTYldAu8utR7HyyfgiJb3FVEqNMfQWy88kazMIPRpokKtBauNSHYnklqP1+aB3S
|
||||
JUqNES93xfNiiux8cj1xwFcQtRZJosh+34di55NE2hc9TRKiiH7fi2J6hdR+n5R+n9pFfobiSlfs
|
||||
fJJI7W2ptUhnKGGeZ9DbulFUr/CJA98lzlA2SYQie9vw83SFz6h6hU+MettNEqKIb3rSzy/m00oT
|
||||
BfW2m2RBKD29La9UvcKHUT65SUqUGiNe3/HzGS2v1G9Kg8icNskMUHh8vR7Fyif5xCjL3iQhytRX
|
||||
ICuf5MMon9wkUYF4gL2y4+eVllcKnoM2yVmg5L45iM9oeYXzSfHCe5OUu5LBHOR4MvMZLa9kPAdt
|
||||
khClZw7ilZZXOJ9EV1BGcxD/3jMH8UrLKxnPQSfJyy5u+X3qm4P4jJZXJpxlb5JyV6b24fiVTcJp
|
||||
pYUCv+nZJEeAAuYgJ4rplZpPyk9GTpKwQGAO8qJYXuHvSvGugDlo+X3s6235jJZXxuoV8bJhk0Qo
|
||||
oLd13eKs1JZPjLLsTRKiyN7WiWJ6ZcS97SYpbcshKvWhWF7h3BYXCPe2/Pnn3IdieUXJbU+SYFdq
|
||||
iHqJ4iGxYls+Lwr4T4qYZBL18ZGYThlwwL8povLUULOLxDLKgIegTVG2cJygnu+Jb/KwQls+ccDl
|
||||
ScoQxAlqD4oV2vKJlSEoKUMQJ6ihC8W0SlKHoIQCfv5dPIDcKJZXkjoEJWUI4gS1c1csr0Qc8G+S
|
||||
clc4Qe1BsUJbPrEyBEVlCIogiPOimF6J6hAUlSEogiDOjWJ5JaqNbVSGIA5tO1EsrwS1sQ1KYxvA
|
||||
K2YfihXa8omVxjaggJ9/F6+YvSimV5TQ9iQJGlv+9rP0oVheCfiDnpMkaOEIBHFuFMsrpHqFUMBf
|
||||
f+8N4qzQlk+MPlY/SWKUqa9AVmjLh9EHPSdJWCAZxLlRLK8Q/lj9dCTGS5Qyt8J1oRheKTP+L3Y3
|
||||
SXG3LTN4GeRCKXZoW2ZtCGLJGBCK+FjdFdoWO7QtszYEFfg/AuDfZRD384+/eOXA/9oaCh6CWDIm
|
||||
gVIT1Bj6UNpEDFGK1ts2SXHjL6U3iCs1tNV3pWhTEEuCK6j0BnGlhrZVS0PBYxBLwgKBIM5VoBra
|
||||
mii4t2XJKG5xJQOvuFGCgZLxBwmbpPRKRi+ZXSg1tK0VUFCU3pYl4a5wgnrlKzJemVlLQ8G9LUsu
|
||||
2/ny58vLyy//Aem67ar/TgAA
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:32 GMT
|
||||
Etag:
|
||||
- W/"4eff-tQtLOmUXB0Jex4vyhYj8l0ja/So"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-WebKit-CSP:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
File diff suppressed because it is too large
Load Diff
1973
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_index_sectors_fetcher.yaml
vendored
Normal file
1973
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_index_sectors_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2101
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_index_snapshots_fetcher.yaml
vendored
Normal file
2101
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_index_snapshots_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyInsidersActivities", "variables": {"symbol":
|
||||
"SHOP"}, "query": "query getCompanyInsidersActivities(\n $symbol: String\n)
|
||||
{\n getCompanyInsidersActivities(\n symbol: $symbol\n ) {\n insiderActivities
|
||||
{\n periodkey\n buy {\n name\n trades\n shares\n sharesHeld\n tradeValue\n }\n sell
|
||||
{\n name\n trades\n shares\n sharesHeld\n tradeValue\n }\n }\n activitySummary
|
||||
{\n periodkey\n buyShares\n soldShares\n netActivity\n totalShares\n }\n }\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA8WVy27bMBBF9/0KQWtB4PvhnZs4dQMEBewgm8ALxqIrNnoEEu1WNfzvpRKnolQD
|
||||
cpui1cqkRvd65syQ+zBRVoWTffhZ24syf1JF87GoTaKrerq2Zmes0XX73rzs+pv3+/BJV6ZMHnUT
|
||||
TkKbVlrflIVN6zAKH7Zu734VhbXOsufYQuXahc3LzSbXpra6ioJrvdm4YFuppFWELjxVVfsTcwhe
|
||||
V3OdJeEEQQboMfZOZVsnhjggVB6iTlxX9nsrW9dmrTxl1CkLiERfGVICRF+ZCiYAj6mnfWWKR525
|
||||
/22KKJirKtNNcGPWqdKZZ0Q7I8kpHRgBTCTrO3GAMccx89NYpqoolLO5LR+aIrhUO5N4HqzzQJRA
|
||||
ifouGFFGSd8FcgYxcLVatTY+ttp8exs0r7QcSzoKjQIMAYkFlseHnwOQdC6QIQBHCUIgAKYyJuS3
|
||||
GUKvD6Gkw0Y8RdEVF3AOYsFes/pPOO1Xne3eOIbcSx/JM+aQS4oAiOFZowixN+WICD6OUgLJMYwZ
|
||||
PtEx5yFFvinDmIwjdXlKLHmHlP0bpJ3JdZkWtS2dy0WZZVoXp49K7r4bpAMhAn15QiB0B1pvGqbL
|
||||
+SwKFp/ezxa3wYfZYrq4PO1AsBwUDEMi0LBgHAoC4h6dpcnLKLgyyRdzWprJIX/UsurpClfDGCHX
|
||||
7K6R1csN1Cy3ea6qZvwaWh6d2hqVWeItC22P91nzvLalVdnP9yMH5d+S/XVg/1h5dTgc3v0A+pNt
|
||||
u9MHAAA=
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 03 Jan 2024 20:08:44 GMT
|
||||
Etag:
|
||||
- W/"7d3-DY9oT4dVvL8jzGyeL78a7Ws2Ogo"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
1282
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_options_chains_fetcher.yaml
vendored
Normal file
1282
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_options_chains_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"operationName": "getCompanyAnalysts", "variables": {"symbol": "SHOP",
|
||||
"datatype": "equity"}, "query": "query getCompanyAnalysts(\n $symbol: String!\n $dataType:
|
||||
String,\n) {\n analysts: getCompanyAnalysts(\n datatype: $dataType,\n symbol:
|
||||
$symbol\n ) {\n totalAnalysts\n priceTarget\n {\n highPriceTarget\n lowPriceTarget\n priceTarget\n priceTargetUpside\n }\n consensusAnalysts\n {\n consensus\n buy\n sell\n hold\n }\n }\n}"}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Content-Type:
|
||||
- application/json
|
||||
authority:
|
||||
- app-money.tmx.com
|
||||
locale:
|
||||
- en
|
||||
referer:
|
||||
- https://money.tmx.com/en/quote/SHOP
|
||||
method: POST
|
||||
uri: https://app-money.tmx.com/graphql
|
||||
response:
|
||||
body:
|
||||
string: '{"data":{"analysts":{"totalAnalysts":31,"priceTarget":{"highPriceTarget":131.9915,"lowPriceTarget":72.595325,"priceTarget":97.8320998,"priceTargetUpside":-3.96},"consensusAnalysts":{"consensus":"Buy","buy":10,"sell":2,"hold":19}}}}
|
||||
|
||||
'
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
- '*'
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '232'
|
||||
Content-Security-Policy:
|
||||
- frame-ancestors 'none'; default-src 'self'
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Date:
|
||||
- Wed, 27 Dec 2023 22:03:36 GMT
|
||||
Etag:
|
||||
- W/"e8-4l0k1dc8UP8MlOmcXd52N+O9B90"
|
||||
Strict-Transport-Security:
|
||||
- max-age=15552000; includeSubDomains
|
||||
Vary:
|
||||
- Origin, Accept-Encoding
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-DNS-Prefetch-Control:
|
||||
- 'off'
|
||||
X-Download-Options:
|
||||
- noopen
|
||||
X-Frame-Options:
|
||||
- SAMEORIGIN
|
||||
X-XSS-Protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
56454
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_treasury_prices_fetcher.yaml
vendored
Normal file
56454
openbb_platform/providers/tmx/tests/record/http/test_tmx_fetchers/test_tmx_treasury_prices_fetcher.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
256
openbb_platform/providers/tmx/tests/test_tmx_fetchers.py
Normal file
256
openbb_platform/providers/tmx/tests/test_tmx_fetchers.py
Normal file
@@ -0,0 +1,256 @@
|
||||
"""TMX fetchers tests."""
|
||||
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
from openbb_core.app.service.user_service import UserService
|
||||
from openbb_tmx.models.available_indices import TmxAvailableIndicesFetcher
|
||||
from openbb_tmx.models.bond_prices import TmxBondPricesFetcher
|
||||
from openbb_tmx.models.calendar_earnings import TmxCalendarEarningsFetcher
|
||||
from openbb_tmx.models.company_filings import TmxCompanyFilingsFetcher
|
||||
from openbb_tmx.models.company_news import TmxCompanyNewsFetcher
|
||||
from openbb_tmx.models.equity_historical import TmxEquityHistoricalFetcher
|
||||
from openbb_tmx.models.equity_profile import TmxEquityProfileFetcher
|
||||
from openbb_tmx.models.equity_quote import TmxEquityQuoteFetcher
|
||||
from openbb_tmx.models.equity_search import TmxEquitySearchFetcher
|
||||
from openbb_tmx.models.etf_countries import TmxEtfCountriesFetcher
|
||||
from openbb_tmx.models.etf_holdings import TmxEtfHoldingsFetcher
|
||||
from openbb_tmx.models.etf_info import TmxEtfInfoFetcher
|
||||
from openbb_tmx.models.etf_search import TmxEtfSearchFetcher
|
||||
from openbb_tmx.models.etf_sectors import TmxEtfSectorsFetcher
|
||||
from openbb_tmx.models.gainers import TmxGainersFetcher
|
||||
from openbb_tmx.models.historical_dividends import TmxHistoricalDividendsFetcher
|
||||
from openbb_tmx.models.index_constituents import TmxIndexConstituentsFetcher
|
||||
from openbb_tmx.models.index_sectors import TmxIndexSectorsFetcher
|
||||
from openbb_tmx.models.index_snapshots import TmxIndexSnapshotsFetcher
|
||||
from openbb_tmx.models.insider_trading import TmxInsiderTradingFetcher
|
||||
from openbb_tmx.models.options_chains import TmxOptionsChainsFetcher
|
||||
from openbb_tmx.models.price_target_consensus import TmxPriceTargetConsensusFetcher
|
||||
from openbb_tmx.models.treasury_prices import TmxTreasuryPricesFetcher
|
||||
|
||||
test_credentials = UserService().default_user_settings.credentials.model_dump()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vcr_config():
|
||||
return {
|
||||
"filter_headers": [("User-Agent", None)],
|
||||
"filter_query_parameters": [
|
||||
None,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_equity_profile_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "RY,NTR"}
|
||||
|
||||
fetcher = TmxEquityProfileFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_equity_search_fetcher(credentials=test_credentials):
|
||||
params = {"query": "gold", "use_cache": False}
|
||||
|
||||
fetcher = TmxEquitySearchFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_available_indices_fetcher(credentials=test_credentials):
|
||||
params = {"use_cache": False}
|
||||
|
||||
fetcher = TmxAvailableIndicesFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_calendar_earnings_fetcher(credentials=test_credentials):
|
||||
params = {"start_date": date(2023, 1, 2), "end_date": date(2023, 1, 31)}
|
||||
|
||||
fetcher = TmxCalendarEarningsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_company_filings_fetcher(credentials=test_credentials):
|
||||
params = {
|
||||
"symbol": "SHOP",
|
||||
"start_date": date(2023, 6, 30),
|
||||
"end_date": date(2023, 9, 30),
|
||||
}
|
||||
|
||||
fetcher = TmxCompanyFilingsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_company_news_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "SHOP", "limit": 5}
|
||||
|
||||
fetcher = TmxCompanyNewsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_equity_historical_fetcher(credentials=test_credentials):
|
||||
params = {
|
||||
"symbol": "SHOP",
|
||||
"start_date": date(2022, 1, 1),
|
||||
"end_date": date(2023, 1, 1),
|
||||
}
|
||||
|
||||
fetcher = TmxEquityHistoricalFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_equity_quote_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "SHOP"}
|
||||
|
||||
fetcher = TmxEquityQuoteFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_etf_countries_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "HXX", "use_cache": False}
|
||||
|
||||
fetcher = TmxEtfCountriesFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_etf_holdings_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "XIU", "use_cache": False}
|
||||
|
||||
fetcher = TmxEtfHoldingsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_etf_info_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "XIU", "use_cache": False}
|
||||
|
||||
fetcher = TmxEtfInfoFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_etf_search_fetcher(credentials=test_credentials):
|
||||
params = {"query": "sector", "use_cache": False}
|
||||
|
||||
fetcher = TmxEtfSearchFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_etf_sectors_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "XIU", "use_cache": False}
|
||||
|
||||
fetcher = TmxEtfSectorsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_gainers_fetcher(credentials=test_credentials):
|
||||
params = {}
|
||||
|
||||
fetcher = TmxGainersFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_historical_dividends_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "TD"}
|
||||
|
||||
fetcher = TmxHistoricalDividendsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_index_constituents_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "^TX60", "use_cache": False}
|
||||
|
||||
fetcher = TmxIndexConstituentsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_index_sectors_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "^TSX", "use_cache": False}
|
||||
|
||||
fetcher = TmxIndexSectorsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_index_snapshots_fetcher(credentials=test_credentials):
|
||||
params = {"use_cache": False}
|
||||
|
||||
fetcher = TmxIndexSnapshotsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_insider_trading_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "SHOP", "summary": False}
|
||||
|
||||
fetcher = TmxInsiderTradingFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_options_chains_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "SHOP", "use_cache": False}
|
||||
|
||||
fetcher = TmxOptionsChainsFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_price_target_consensus_fetcher(credentials=test_credentials):
|
||||
params = {"symbol": "SHOP"}
|
||||
|
||||
fetcher = TmxPriceTargetConsensusFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_treasury_prices_fetcher(credentials=test_credentials):
|
||||
params = {"govt_type": "federal", "use_cache": False}
|
||||
|
||||
fetcher = TmxTreasuryPricesFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.record_http
|
||||
def test_tmx_bond_prices_fetcher(credentials=test_credentials):
|
||||
params = {"use_cache": False, "coupon_rate_min": 4}
|
||||
|
||||
fetcher = TmxBondPricesFetcher()
|
||||
result = fetcher.test(params, credentials)
|
||||
assert result is None
|
||||
@@ -76,7 +76,7 @@ class YFinanceBalanceSheetFetcher(
|
||||
raise EmptyDataError()
|
||||
data.index = [to_snake_case(i) for i in data.index]
|
||||
data = data.reset_index().sort_index(ascending=False).set_index("index")
|
||||
data = data.convert_dtypes().fillna(0).replace(0, None).to_dict()
|
||||
data = data.fillna("N/A").replace("N/A", None).to_dict()
|
||||
data = [{"period_ending": str(key), **value} for key, value in data.items()]
|
||||
|
||||
data = json.loads(json.dumps(data))
|
||||
|
||||
@@ -75,7 +75,7 @@ class YFinanceCashFlowStatementFetcher(
|
||||
|
||||
data.index = [to_snake_case(i) for i in data.index]
|
||||
data = data.reset_index().sort_index(ascending=False).set_index("index")
|
||||
data = data.convert_dtypes().fillna(0).replace(0, None).to_dict()
|
||||
data = data.fillna("N/A").replace("N/A", None).to_dict()
|
||||
data = [{"period_ending": str(key), **value} for key, value in data.items()]
|
||||
|
||||
data = json.loads(json.dumps(data))
|
||||
|
||||
@@ -75,7 +75,7 @@ class YFinanceIncomeStatementFetcher(
|
||||
raise EmptyDataError()
|
||||
data.index = [to_snake_case(i) for i in data.index]
|
||||
data = data.reset_index().sort_index(ascending=False).set_index("index")
|
||||
data = data.convert_dtypes().fillna(0).replace(0, None).to_dict()
|
||||
data = data.fillna("N/A").replace("N/A", None).to_dict()
|
||||
data = [{"period_ending": str(key), **value} for key, value in data.items()]
|
||||
|
||||
data = json.loads(json.dumps(data))
|
||||
|
||||
3
openbb_platform/pyproject.toml
vendored
3
openbb_platform/pyproject.toml
vendored
@@ -45,6 +45,7 @@ openbb-government-us = { version = "^1.1.2", optional = true }
|
||||
openbb-nasdaq = { version = "^1.1.3", optional = true }
|
||||
openbb-seeking-alpha = { version = "^1.1.2", optional = true }
|
||||
openbb-stockgrid = { version = "^1.1.2", optional = true }
|
||||
openbb-tmx = { version = "^1.0.0", optional = true }
|
||||
openbb-wsj = { version = "^1.1.2", optional = true }
|
||||
|
||||
openbb-charting = { version = "^2.0.0", optional = true }
|
||||
@@ -67,6 +68,7 @@ quantitative = ["openbb-quantitative"]
|
||||
seeking_alpha = ["openbb-seeking-alpha"]
|
||||
stockgrid = ["openbb-stockgrid"]
|
||||
technical = ["openbb-technical"]
|
||||
tmx = ["openbb-tmx"]
|
||||
wsj = ["openbb-wsj"]
|
||||
|
||||
|
||||
@@ -85,6 +87,7 @@ all = [
|
||||
"openbb-seeking-alpha",
|
||||
"openbb-stockgrid",
|
||||
"openbb-technical",
|
||||
"openbb-tmx",
|
||||
"openbb-wsj",
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user