openbb-aurastream 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ """OpenBB AuraStream Provider Module."""
2
+
3
+ from openbb_core.provider.abstract.provider import Provider
4
+
5
+ from openbb_aurastream.models.fails_to_deliver import AuraStreamFailsToDeliverFetcher
6
+ from openbb_aurastream.models.fred_vintages import AuraStreamFredVintagesFetcher
7
+ from openbb_aurastream.models.funding_rates import AuraStreamFundingRatesFetcher
8
+ from openbb_aurastream.models.net_short import AuraStreamNetShortFetcher
9
+ from openbb_aurastream.models.wasde import AuraStreamWasdeFetcher
10
+
11
+ aurastream_provider = Provider(
12
+ name="aurastream",
13
+ website="https://aurastream.unbiased-alpha.com",
14
+ description="AuraStream, a unified market data API for quants by Unbiased Alpha:"
15
+ " EU net short positions, USDA WASDE projections, FRED with every revision,"
16
+ " SEC fails-to-deliver and Binance funding rates.",
17
+ credentials=["api_key", "fred_api_key", "sec_user_agent"],
18
+ fetcher_dict={
19
+ "AuraStreamNetShort": AuraStreamNetShortFetcher,
20
+ "AuraStreamWasde": AuraStreamWasdeFetcher,
21
+ "AuraStreamFredVintages": AuraStreamFredVintagesFetcher,
22
+ "AuraStreamFailsToDeliver": AuraStreamFailsToDeliverFetcher,
23
+ "AuraStreamFundingRates": AuraStreamFundingRatesFetcher,
24
+ },
25
+ repr_name="AuraStream",
26
+ instructions="Get a free API key (500 calls a month, no credit card) at"
27
+ " https://aurastream.unbiased-alpha.com/sign-up and set it as aurastream_api_key."
28
+ " fred_vintages also needs your own free FRED key (aurastream_fred_api_key), and"
29
+ " fails_to_deliver a contact string for the SEC (aurastream_sec_user_agent),"
30
+ " e.g. 'Jane Doe jane@example.com'.",
31
+ )
@@ -0,0 +1,117 @@
1
+ """AuraStream Router."""
2
+
3
+ from openbb_core.app.model.command_context import CommandContext
4
+ from openbb_core.app.model.example import APIEx
5
+ from openbb_core.app.model.obbject import OBBject
6
+ from openbb_core.app.provider_interface import (
7
+ ExtraParams,
8
+ ProviderChoices,
9
+ StandardParams,
10
+ )
11
+ from openbb_core.app.query import Query
12
+ from openbb_core.app.router import Router
13
+
14
+ router = Router(prefix="", description="AuraStream market data.")
15
+
16
+
17
+ @router.command(
18
+ model="AuraStreamNetShort",
19
+ examples=[
20
+ APIEx(
21
+ description="Disclosed short positions in Ubisoft on the French register.",
22
+ parameters={"symbol": "ubisoft", "country": "FR", "provider": "aurastream"},
23
+ ),
24
+ APIEx(
25
+ description="Every position one fund has disclosed in the period.",
26
+ parameters={"symbol": "all", "holder": "citadel", "provider": "aurastream"},
27
+ ),
28
+ ],
29
+ )
30
+ async def net_short(
31
+ cc: CommandContext,
32
+ provider_choices: ProviderChoices,
33
+ standard_params: StandardParams,
34
+ extra_params: ExtraParams,
35
+ ) -> OBBject:
36
+ """Get disclosed net short positions of 0.5% or more in European shares (AMF, AFM, CNMV)."""
37
+ return await OBBject.from_query(Query(**locals()))
38
+
39
+
40
+ @router.command(
41
+ model="AuraStreamWasde",
42
+ examples=[
43
+ APIEx(
44
+ description="How the USDA's projection of US corn ending stocks changed, report by report.",
45
+ parameters={"symbol": "corn:ending_stocks", "provider": "aurastream"},
46
+ ),
47
+ APIEx(
48
+ description="Brazil's soybean crop from the world tables.",
49
+ parameters={"symbol": "soybeans:production:Brazil", "provider": "aurastream"},
50
+ ),
51
+ ],
52
+ )
53
+ async def wasde(
54
+ cc: CommandContext,
55
+ provider_choices: ProviderChoices,
56
+ standard_params: StandardParams,
57
+ extra_params: ExtraParams,
58
+ ) -> OBBject:
59
+ """Get USDA WASDE projections since July 2010, one row per monthly report."""
60
+ return await OBBject.from_query(Query(**locals()))
61
+
62
+
63
+ @router.command(
64
+ model="AuraStreamFredVintages",
65
+ examples=[
66
+ APIEx(
67
+ description="Every published version of US CPI, by publication date.",
68
+ parameters={"symbol": "CPIAUCSL", "provider": "aurastream"},
69
+ ),
70
+ ],
71
+ )
72
+ async def fred_vintages(
73
+ cc: CommandContext,
74
+ provider_choices: ProviderChoices,
75
+ standard_params: StandardParams,
76
+ extra_params: ExtraParams,
77
+ ) -> OBBject:
78
+ """Get FRED series with every revision, stamped at the date each value was published."""
79
+ return await OBBject.from_query(Query(**locals()))
80
+
81
+
82
+ @router.command(
83
+ model="AuraStreamFailsToDeliver",
84
+ examples=[
85
+ APIEx(
86
+ description="Fails-to-deliver in GameStop.",
87
+ parameters={"symbol": "GME", "provider": "aurastream"},
88
+ ),
89
+ ],
90
+ )
91
+ async def fails_to_deliver(
92
+ cc: CommandContext,
93
+ provider_choices: ProviderChoices,
94
+ standard_params: StandardParams,
95
+ extra_params: ExtraParams,
96
+ ) -> OBBject:
97
+ """Get SEC fails-to-deliver for a US stock or ETF, one row per settlement date."""
98
+ return await OBBject.from_query(Query(**locals()))
99
+
100
+
101
+ @router.command(
102
+ model="AuraStreamFundingRates",
103
+ examples=[
104
+ APIEx(
105
+ description="Funding rates of the Bitcoin and Ether perpetuals.",
106
+ parameters={"symbol": "BTCUSDT,ETHUSDT", "provider": "aurastream"},
107
+ ),
108
+ ],
109
+ )
110
+ async def funding_rates(
111
+ cc: CommandContext,
112
+ provider_choices: ProviderChoices,
113
+ standard_params: StandardParams,
114
+ extra_params: ExtraParams,
115
+ ) -> OBBject:
116
+ """Get Binance USD-M perpetual funding rates at each funding time."""
117
+ return await OBBject.from_query(Query(**locals()))
@@ -0,0 +1 @@
1
+ """AuraStream models."""
@@ -0,0 +1,99 @@
1
+ """AuraStream Fails-to-Deliver Model."""
2
+
3
+ from datetime import date as dateType, datetime
4
+ from typing import Any
5
+
6
+ from openbb_core.provider.abstract.data import Data
7
+ from openbb_core.provider.abstract.fetcher import Fetcher
8
+ from openbb_core.provider.standard_models.bls_series import SeriesQueryParams
9
+ from pydantic import Field
10
+
11
+ from openbb_aurastream.utils.client import (
12
+ api_key,
13
+ default_start,
14
+ fetch,
15
+ require_key,
16
+ split_symbols,
17
+ to_rows,
18
+ )
19
+
20
+
21
+ class AuraStreamFailsToDeliverQueryParams(SeriesQueryParams):
22
+ """AuraStream Fails-to-Deliver Query.
23
+
24
+ Source: https://aurastream.unbiased-alpha.com/docs/data-sources/edgar/
25
+ """
26
+
27
+ __json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
28
+
29
+ symbol: str = Field(
30
+ description="US stock or ETF symbol (e.g. 'GME') or 9-character CUSIP."
31
+ )
32
+ start_date: dateType | None = Field(
33
+ default=None, description="Start date. Default: one year ago."
34
+ )
35
+ end_date: dateType | None = Field(
36
+ default=None, description="End date. Default: today."
37
+ )
38
+
39
+
40
+ class AuraStreamFailsToDeliverData(Data):
41
+ """AuraStream Fails-to-Deliver Data. One row per settlement date with fails outstanding."""
42
+
43
+ date: datetime = Field(description="Settlement date.")
44
+ symbol: str = Field(description="Security symbol.")
45
+ fails: int | None = Field(
46
+ default=None, description="Total fails-to-deliver balance, in shares."
47
+ )
48
+ price: float | None = Field(
49
+ default=None, description="Previous day's closing price."
50
+ )
51
+ cusip: str | None = Field(default=None, description="CUSIP of the security.")
52
+ description: str | None = Field(default=None, description="Security description.")
53
+
54
+
55
+ class AuraStreamFailsToDeliverFetcher(
56
+ Fetcher[AuraStreamFailsToDeliverQueryParams, list[AuraStreamFailsToDeliverData]]
57
+ ):
58
+ """Fetch SEC fails-to-deliver from AuraStream."""
59
+
60
+ require_credentials = False
61
+
62
+ @staticmethod
63
+ def transform_query(params: dict[str, Any]) -> AuraStreamFailsToDeliverQueryParams:
64
+ """Transform the query."""
65
+ return AuraStreamFailsToDeliverQueryParams(**params)
66
+
67
+ @staticmethod
68
+ async def aextract_data(
69
+ query: AuraStreamFailsToDeliverQueryParams,
70
+ credentials: dict[str, str] | None,
71
+ **kwargs: Any,
72
+ ) -> list[dict]:
73
+ """Return the raw rows."""
74
+ key = api_key(credentials)
75
+ user_agent = require_key(
76
+ credentials,
77
+ "aurastream_sec_user_agent",
78
+ "The SEC asks every automated request to name the sender: your name or company"
79
+ " and a contact email, e.g. 'Jane Doe jane@example.com'.",
80
+ )
81
+ symbols = [s.upper() for s in split_symbols(query.symbol)]
82
+ body = {
83
+ "tickerlist": symbols,
84
+ "start": str(query.start_date or default_start()),
85
+ "end": str(query.end_date or dateType.today()),
86
+ "interval": "1d",
87
+ "source": "edgar",
88
+ "dataset": "ftd",
89
+ "credentials": {"user_agent": user_agent},
90
+ }
91
+ data = await fetch(body, key)
92
+ return to_rows(data, symbols)
93
+
94
+ @staticmethod
95
+ def transform_data(
96
+ query: AuraStreamFailsToDeliverQueryParams, data: list[dict], **kwargs: Any
97
+ ) -> list[AuraStreamFailsToDeliverData]:
98
+ """Transform the data."""
99
+ return [AuraStreamFailsToDeliverData.model_validate(d) for d in data]
@@ -0,0 +1,112 @@
1
+ """AuraStream FRED Vintages Model."""
2
+
3
+ from datetime import date as dateType, datetime
4
+ from typing import Any
5
+
6
+ from openbb_core.provider.abstract.data import Data
7
+ from openbb_core.provider.abstract.fetcher import Fetcher
8
+ from openbb_core.provider.standard_models.bls_series import SeriesQueryParams
9
+ from openbb_core.provider.utils.errors import EmptyDataError
10
+ from pydantic import Field
11
+
12
+ from openbb_aurastream.utils.client import (
13
+ api_key,
14
+ default_start,
15
+ fetch,
16
+ parse_timestamp,
17
+ require_key,
18
+ split_symbols,
19
+ )
20
+
21
+
22
+ class AuraStreamFredVintagesQueryParams(SeriesQueryParams):
23
+ """AuraStream FRED Vintages Query.
24
+
25
+ Source: https://aurastream.unbiased-alpha.com/docs/data-sources/fred/
26
+ """
27
+
28
+ __json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
29
+
30
+ symbol: str = Field(
31
+ description="FRED series ID, e.g. 'CPIAUCSL', 'UNRATE', 'GDPC1'."
32
+ )
33
+ start_date: dateType | None = Field(
34
+ default=None,
35
+ description="First publication date. Default: one year ago.",
36
+ )
37
+ end_date: dateType | None = Field(
38
+ default=None, description="Last publication date. Default: today."
39
+ )
40
+
41
+
42
+ class AuraStreamFredVintagesData(Data):
43
+ """AuraStream FRED Vintages Data. One row per published value."""
44
+
45
+ date: datetime = Field(description="Publication date of the value.")
46
+ symbol: str = Field(description="FRED series ID.")
47
+ reference_date: datetime = Field(description="The period the value describes.")
48
+ value: float | None = Field(default=None, description="The value as published.")
49
+
50
+
51
+ class AuraStreamFredVintagesFetcher(
52
+ Fetcher[AuraStreamFredVintagesQueryParams, list[AuraStreamFredVintagesData]]
53
+ ):
54
+ """Fetch every published version of FRED series from AuraStream."""
55
+
56
+ require_credentials = False
57
+
58
+ @staticmethod
59
+ def transform_query(params: dict[str, Any]) -> AuraStreamFredVintagesQueryParams:
60
+ """Transform the query."""
61
+ return AuraStreamFredVintagesQueryParams(**params)
62
+
63
+ @staticmethod
64
+ async def aextract_data(
65
+ query: AuraStreamFredVintagesQueryParams,
66
+ credentials: dict[str, str] | None,
67
+ **kwargs: Any,
68
+ ) -> list[dict]:
69
+ """Return one row per (publication date, period)."""
70
+ key = api_key(credentials)
71
+ fred_key = require_key(
72
+ credentials,
73
+ "aurastream_fred_api_key",
74
+ "FRED needs a free key of your own: https://fred.stlouisfed.org/docs/api/api_key.html.",
75
+ )
76
+ symbols = [s.upper() for s in split_symbols(query.symbol)]
77
+ body = {
78
+ "tickerlist": symbols,
79
+ "start": str(query.start_date or default_start()),
80
+ "end": str(query.end_date or dateType.today()),
81
+ "interval": "1d", # ignored: each series comes at its own frequency
82
+ "source": "fred",
83
+ "credentials": {"key": fred_key},
84
+ }
85
+ data = await fetch(body, key)
86
+
87
+ rows: list[dict] = []
88
+ for symbol in symbols:
89
+ series = data.get(symbol)
90
+ if not isinstance(series, dict):
91
+ continue
92
+ for published, row in series.items():
93
+ for update in (row or {}).get("updates") or []:
94
+ rows.append(
95
+ {
96
+ "date": parse_timestamp(published),
97
+ "symbol": symbol,
98
+ "reference_date": parse_timestamp(update["reference_date"]),
99
+ "value": update.get("value"),
100
+ }
101
+ )
102
+ if not rows:
103
+ raise EmptyDataError(f"AuraStream returned no data for {', '.join(symbols)}.")
104
+ rows.sort(key=lambda r: (r["date"], r["symbol"], r["reference_date"]))
105
+ return rows
106
+
107
+ @staticmethod
108
+ def transform_data(
109
+ query: AuraStreamFredVintagesQueryParams, data: list[dict], **kwargs: Any
110
+ ) -> list[AuraStreamFredVintagesData]:
111
+ """Transform the data."""
112
+ return [AuraStreamFredVintagesData.model_validate(d) for d in data]
@@ -0,0 +1,90 @@
1
+ """AuraStream Binance Funding Rates Model."""
2
+
3
+ from datetime import date as dateType, datetime
4
+ from typing import Any
5
+
6
+ from openbb_core.provider.abstract.data import Data
7
+ from openbb_core.provider.abstract.fetcher import Fetcher
8
+ from openbb_core.provider.standard_models.bls_series import SeriesQueryParams
9
+ from pydantic import Field
10
+
11
+ from openbb_aurastream.utils.client import (
12
+ api_key,
13
+ default_start,
14
+ fetch,
15
+ split_symbols,
16
+ to_rows,
17
+ )
18
+
19
+
20
+ class AuraStreamFundingRatesQueryParams(SeriesQueryParams):
21
+ """AuraStream Binance Funding Rates Query.
22
+
23
+ Source: https://aurastream.unbiased-alpha.com/docs/data-sources/binance/
24
+ """
25
+
26
+ __json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
27
+
28
+ symbol: str = Field(
29
+ default="BTCUSDT",
30
+ description="Binance USD-M perpetual symbol, e.g. 'BTCUSDT', 'ETHUSDT'.",
31
+ )
32
+ start_date: dateType | None = Field(
33
+ default=None, description="Start date. Default: one year ago."
34
+ )
35
+ end_date: dateType | None = Field(
36
+ default=None, description="End date. Default: today."
37
+ )
38
+
39
+
40
+ class AuraStreamFundingRatesData(Data):
41
+ """AuraStream Binance Funding Rates Data. One row per funding time."""
42
+
43
+ __alias_dict__ = {"funding_rate": "fundingRate"}
44
+
45
+ date: datetime = Field(description="Funding time.")
46
+ symbol: str = Field(description="Perpetual symbol.")
47
+ funding_rate: float | None = Field(
48
+ default=None,
49
+ description="Funding rate for the period, as a fraction (0.0001 = 0.01%).",
50
+ )
51
+
52
+
53
+ class AuraStreamFundingRatesFetcher(
54
+ Fetcher[AuraStreamFundingRatesQueryParams, list[AuraStreamFundingRatesData]]
55
+ ):
56
+ """Fetch Binance perpetual funding rates from AuraStream."""
57
+
58
+ require_credentials = False
59
+
60
+ @staticmethod
61
+ def transform_query(params: dict[str, Any]) -> AuraStreamFundingRatesQueryParams:
62
+ """Transform the query."""
63
+ return AuraStreamFundingRatesQueryParams(**params)
64
+
65
+ @staticmethod
66
+ async def aextract_data(
67
+ query: AuraStreamFundingRatesQueryParams,
68
+ credentials: dict[str, str] | None,
69
+ **kwargs: Any,
70
+ ) -> list[dict]:
71
+ """Return the raw rows."""
72
+ symbols = [s.upper() for s in split_symbols(query.symbol)]
73
+ body = {
74
+ "tickerlist": symbols,
75
+ "start": str(query.start_date or default_start()),
76
+ "end": str(query.end_date or dateType.today()),
77
+ "interval": "1h",
78
+ "source": "binance",
79
+ "instrument": "futures",
80
+ "bars": "funding",
81
+ }
82
+ data = await fetch(body, api_key(credentials))
83
+ return to_rows(data, symbols)
84
+
85
+ @staticmethod
86
+ def transform_data(
87
+ query: AuraStreamFundingRatesQueryParams, data: list[dict], **kwargs: Any
88
+ ) -> list[AuraStreamFundingRatesData]:
89
+ """Transform the data."""
90
+ return [AuraStreamFundingRatesData.model_validate(d) for d in data]
@@ -0,0 +1,124 @@
1
+ """AuraStream EU Net Short Positions Model."""
2
+
3
+ from datetime import date as dateType, datetime
4
+ from typing import Any
5
+
6
+ from openbb_core.provider.abstract.data import Data
7
+ from openbb_core.provider.abstract.fetcher import Fetcher
8
+ from openbb_core.provider.standard_models.bls_series import SeriesQueryParams
9
+ from pydantic import Field, field_validator
10
+
11
+ from openbb_aurastream.utils.client import (
12
+ api_key,
13
+ default_start,
14
+ fetch,
15
+ split_symbols,
16
+ to_rows,
17
+ )
18
+
19
+
20
+ class AuraStreamNetShortQueryParams(SeriesQueryParams):
21
+ """AuraStream EU Net Short Positions Query.
22
+
23
+ Source: https://aurastream.unbiased-alpha.com/docs/data-sources/netshort/
24
+ """
25
+
26
+ __json_schema_extra__ = {
27
+ "symbol": {"multiple_items_allowed": True},
28
+ "country": {"multiple_items_allowed": True, "choices": ["FR", "NL", "ES"]},
29
+ }
30
+
31
+ symbol: str = Field(
32
+ description="ISIN (e.g. 'FR0000054470'), part of the issuer's name (e.g. 'ubisoft'),"
33
+ + " or 'all' for every position in the period.",
34
+ )
35
+ start_date: dateType | None = Field(
36
+ default=None, description="Start date. Default: one year ago."
37
+ )
38
+ end_date: dateType | None = Field(
39
+ default=None, description="End date. Default: today."
40
+ )
41
+ country: str | None = Field(
42
+ default=None,
43
+ description="Registers to search: FR (AMF), NL (AFM), ES (CNMV). Default: all three.",
44
+ )
45
+ holder: str | None = Field(
46
+ default=None,
47
+ description="Keep positions whose holder name contains this text, e.g. 'citadel'.",
48
+ )
49
+
50
+
51
+ @field_validator("symbol", mode="before", check_fields=False)
52
+ @classmethod
53
+ def to_upper(cls, v: str) -> str:
54
+ """Keep the symbol as written: these tickers are case-sensitive."""
55
+ return v
56
+
57
+
58
+ class AuraStreamNetShortData(Data):
59
+ """AuraStream EU Net Short Positions Data. One row per published position."""
60
+
61
+ date: datetime = Field(description="Position date.")
62
+ symbol: str = Field(description="The symbol as requested.")
63
+ holder: str | None = Field(default=None, description="Position holder.")
64
+ issuer: str | None = Field(default=None, description="Company name.")
65
+ isin: str | None = Field(default=None, description="Company ISIN.")
66
+ net_short_pct: float | None = Field(
67
+ default=None,
68
+ description="Net short position, in percent of issued share capital (0.62 = 0.62%).",
69
+ )
70
+ country: str | None = Field(default=None, description="FR, NL or ES.")
71
+ holder_lei: str | None = Field(
72
+ default=None, description="Holder's Legal Entity Identifier (France only)."
73
+ )
74
+ published_from: str | None = Field(
75
+ default=None, description="First date on the public register (France only)."
76
+ )
77
+ published_to: str | None = Field(
78
+ default=None, description="Last date on the public register (France only)."
79
+ )
80
+ source: str | None = Field(
81
+ default=None, description="The attribution the regulator requires."
82
+ )
83
+
84
+
85
+ class AuraStreamNetShortFetcher(
86
+ Fetcher[AuraStreamNetShortQueryParams, list[AuraStreamNetShortData]]
87
+ ):
88
+ """Fetch EU net short positions from AuraStream."""
89
+
90
+ require_credentials = False
91
+
92
+ @staticmethod
93
+ def transform_query(params: dict[str, Any]) -> AuraStreamNetShortQueryParams:
94
+ """Transform the query."""
95
+ return AuraStreamNetShortQueryParams(**params)
96
+
97
+ @staticmethod
98
+ async def aextract_data(
99
+ query: AuraStreamNetShortQueryParams,
100
+ credentials: dict[str, str] | None,
101
+ **kwargs: Any,
102
+ ) -> list[dict]:
103
+ """Return the raw rows."""
104
+ symbols = split_symbols(query.symbol)
105
+ body: dict[str, Any] = {
106
+ "tickerlist": symbols,
107
+ "start": str(query.start_date or default_start()),
108
+ "end": str(query.end_date or dateType.today()),
109
+ "interval": "1d",
110
+ "source": "netshort",
111
+ }
112
+ if query.country:
113
+ body["country"] = [c.upper() for c in split_symbols(query.country)]
114
+ if query.holder:
115
+ body["holder"] = query.holder
116
+ data = await fetch(body, api_key(credentials))
117
+ return to_rows(data, symbols)
118
+
119
+ @staticmethod
120
+ def transform_data(
121
+ query: AuraStreamNetShortQueryParams, data: list[dict], **kwargs: Any
122
+ ) -> list[AuraStreamNetShortData]:
123
+ """Transform the data."""
124
+ return [AuraStreamNetShortData.model_validate(d) for d in data]
@@ -0,0 +1,104 @@
1
+ """AuraStream USDA WASDE Model."""
2
+
3
+ from datetime import date as dateType, datetime
4
+ from typing import Any
5
+
6
+ from openbb_core.provider.abstract.data import Data
7
+ from openbb_core.provider.abstract.fetcher import Fetcher
8
+ from openbb_core.provider.standard_models.bls_series import SeriesQueryParams
9
+ from pydantic import Field, field_validator
10
+
11
+ from openbb_aurastream.utils.client import (
12
+ api_key,
13
+ default_start,
14
+ fetch,
15
+ split_symbols,
16
+ to_rows,
17
+ )
18
+
19
+
20
+ class AuraStreamWasdeQueryParams(SeriesQueryParams):
21
+ """AuraStream USDA WASDE Query.
22
+
23
+ Source: https://aurastream.unbiased-alpha.com/docs/data-sources/wasde/
24
+ """
25
+
26
+ __json_schema_extra__ = {"symbol": {"multiple_items_allowed": True}}
27
+
28
+ symbol: str = Field(
29
+ description="'commodity:attribute' for the US balance sheet (e.g. 'corn:ending_stocks'),"
30
+ + " or 'commodity:attribute:region' for the world tables (e.g. 'soybeans:production:Brazil').",
31
+ )
32
+ start_date: dateType | None = Field(
33
+ default=None, description="Start date. Default: one year ago."
34
+ )
35
+ end_date: dateType | None = Field(
36
+ default=None, description="End date. Default: today."
37
+ )
38
+
39
+
40
+ @field_validator("symbol", mode="before", check_fields=False)
41
+ @classmethod
42
+ def to_upper(cls, v: str) -> str:
43
+ """Keep the symbol as written: these tickers are case-sensitive."""
44
+ return v
45
+
46
+
47
+ class AuraStreamWasdeData(Data):
48
+ """AuraStream USDA WASDE Data. One row per report."""
49
+
50
+ date: datetime = Field(description="Report release time (12:00 New York).")
51
+ symbol: str = Field(description="The series as requested.")
52
+ report: str | None = Field(
53
+ default=None, description="Report month, e.g. 'October 2026'."
54
+ )
55
+ market_year: str | None = Field(
56
+ default=None, description="Marketing year projected, e.g. '2026/27'."
57
+ )
58
+ value: float | None = Field(default=None, description="This report's projection.")
59
+ previous: float | None = Field(
60
+ default=None,
61
+ description="The previous month's projection, as printed in the same report.",
62
+ )
63
+ prior_year: str | None = Field(default=None, description="The year before.")
64
+ prior_year_value: float | None = Field(
65
+ default=None, description="The estimate for the year before."
66
+ )
67
+
68
+
69
+ class AuraStreamWasdeFetcher(
70
+ Fetcher[AuraStreamWasdeQueryParams, list[AuraStreamWasdeData]]
71
+ ):
72
+ """Fetch WASDE projections from AuraStream."""
73
+
74
+ require_credentials = False
75
+
76
+ @staticmethod
77
+ def transform_query(params: dict[str, Any]) -> AuraStreamWasdeQueryParams:
78
+ """Transform the query."""
79
+ return AuraStreamWasdeQueryParams(**params)
80
+
81
+ @staticmethod
82
+ async def aextract_data(
83
+ query: AuraStreamWasdeQueryParams,
84
+ credentials: dict[str, str] | None,
85
+ **kwargs: Any,
86
+ ) -> list[dict]:
87
+ """Return the raw rows."""
88
+ symbols = split_symbols(query.symbol)
89
+ body = {
90
+ "tickerlist": symbols,
91
+ "start": str(query.start_date or default_start()),
92
+ "end": str(query.end_date or dateType.today()),
93
+ "interval": "1m",
94
+ "source": "wasde",
95
+ }
96
+ data = await fetch(body, api_key(credentials))
97
+ return to_rows(data, symbols)
98
+
99
+ @staticmethod
100
+ def transform_data(
101
+ query: AuraStreamWasdeQueryParams, data: list[dict], **kwargs: Any
102
+ ) -> list[AuraStreamWasdeData]:
103
+ """Transform the data."""
104
+ return [AuraStreamWasdeData.model_validate(d) for d in data]
@@ -0,0 +1 @@
1
+ """AuraStream utilities."""
@@ -0,0 +1,148 @@
1
+ """Thin async client for the AuraStream /timeseries endpoint."""
2
+
3
+ import os
4
+ from datetime import date, datetime, timedelta
5
+ from typing import Any
6
+
7
+ from openbb_core.app.model.abstract.error import OpenBBError
8
+ from openbb_core.provider.utils.errors import EmptyDataError, UnauthorizedError
9
+
10
+ API_URL = os.environ.get(
11
+ "AURASTREAM_API_URL", "https://api.aurastream.unbiased-alpha.com/timeseries"
12
+ )
13
+ DASHBOARD_URL = "https://aurastream.unbiased-alpha.com"
14
+ TIMEOUT_SECONDS = 130 # the API itself times out at 120 s
15
+
16
+
17
+ def default_start() -> date:
18
+ """One year back, the default start of a query."""
19
+ return date.today() - timedelta(days=365)
20
+
21
+
22
+ def require_key(credentials: dict[str, str] | None, name: str, hint: str) -> str:
23
+ """Return a credential or raise an error saying how to set it."""
24
+ value = (credentials or {}).get(name)
25
+ if not value:
26
+ raise UnauthorizedError(
27
+ f"Missing credential '{name}'. {hint} Set it with "
28
+ f'obb.user.credentials.{name} = "..." or in ~/.openbb_platform/user_settings.json.'
29
+ )
30
+ return value
31
+
32
+
33
+ def api_key(credentials: dict[str, str] | None) -> str:
34
+ """The AuraStream API key."""
35
+ return require_key(
36
+ credentials,
37
+ "aurastream_api_key",
38
+ f"Get a free key (500 calls a month, no credit card) at {DASHBOARD_URL}/sign-up.",
39
+ )
40
+
41
+
42
+ def _error_message(status: int, detail: str) -> str:
43
+ if status == 429:
44
+ if "limit exceeded" in detail.lower():
45
+ return (
46
+ "AuraStream monthly call allowance is used up. It resets on the 1st of next month "
47
+ f"(00:00 UTC); to keep going now, upgrade the plan at {DASHBOARD_URL}."
48
+ )
49
+ return "AuraStream rate limit hit. Wait a few seconds and retry."
50
+ if status in (401, 403):
51
+ return (
52
+ f"AuraStream rejected the API key (HTTP {status}). "
53
+ f"Check aurastream_api_key; keys are on the dashboard at {DASHBOARD_URL}."
54
+ )
55
+ if status == 400:
56
+ return f"AuraStream rejected the request (HTTP 400): {detail or 'no detail'}."
57
+ if status in (502, 504):
58
+ return (
59
+ f"AuraStream timed out (HTTP {status}). "
60
+ "Try a shorter date range or fewer symbols."
61
+ )
62
+ return f"AuraStream returned HTTP {status}: {detail or 'no detail'}"
63
+
64
+
65
+ async def fetch(body: dict[str, Any], key: str) -> dict[str, Any]:
66
+ """POST the body to AuraStream and return the parsed JSON."""
67
+ import aiohttp
68
+
69
+ timeout = aiohttp.ClientTimeout(total=TIMEOUT_SECONDS)
70
+ try:
71
+ async with aiohttp.ClientSession(timeout=timeout) as session:
72
+ async with session.post(
73
+ API_URL, json=body, headers={"x-api-key": key}
74
+ ) as resp:
75
+ status = resp.status
76
+ text = await resp.text()
77
+ except TimeoutError:
78
+ raise OpenBBError(
79
+ "AuraStream did not answer in time. Try a shorter date range or fewer symbols."
80
+ ) from None
81
+ except aiohttp.ClientError as exc:
82
+ raise OpenBBError(f"Could not reach AuraStream: {exc}") from None
83
+
84
+ import json
85
+
86
+ try:
87
+ data = json.loads(text)
88
+ except ValueError:
89
+ data = None
90
+
91
+ if status != 200:
92
+ detail = ""
93
+ if isinstance(data, dict):
94
+ detail = str(
95
+ data.get("message") or data.get("error") or data.get("detail") or ""
96
+ )
97
+ elif text:
98
+ detail = text.strip()
99
+ message = _error_message(status, detail[:300])
100
+ if status in (401, 403):
101
+ raise UnauthorizedError(message)
102
+ raise OpenBBError(message)
103
+
104
+ if not isinstance(data, dict):
105
+ raise OpenBBError("AuraStream returned a response that is not a JSON object.")
106
+ # Errors that come back with a 200, e.g. {"error": "..."}.
107
+ if len(data) == 1:
108
+ k, v = next(iter(data.items()))
109
+ if k in ("error", "message") and isinstance(v, str):
110
+ raise OpenBBError(f"AuraStream error: {v[:300]}")
111
+ return data
112
+
113
+
114
+ def parse_timestamp(value: str) -> datetime:
115
+ """Parse a row key such as '2025-02-12 00:00:00' or '2025-02-12 00:00:00.000001'."""
116
+ return datetime.fromisoformat(value.replace("Z", "+00:00"))
117
+
118
+
119
+ def to_rows(data: dict[str, Any], symbols: list[str]) -> list[dict[str, Any]]:
120
+ """Flatten {symbol: {timestamp: fields}} into rows with 'date' and 'symbol'.
121
+
122
+ Raises EmptyDataError when no symbol returned any row.
123
+ """
124
+ rows: list[dict[str, Any]] = []
125
+ errors: list[str] = []
126
+ for symbol in symbols:
127
+ series = data.get(symbol)
128
+ if isinstance(series, str):
129
+ errors.append(f"{symbol}: {series[:200]}")
130
+ continue
131
+ if not isinstance(series, dict):
132
+ continue
133
+ for ts, fields in series.items():
134
+ if not isinstance(fields, dict):
135
+ continue
136
+ row = {"date": parse_timestamp(ts), "symbol": symbol}
137
+ row.update(fields)
138
+ rows.append(row)
139
+ if not rows:
140
+ extra = f" ({'; '.join(errors)})" if errors else ""
141
+ raise EmptyDataError(f"AuraStream returned no data for {', '.join(symbols)}{extra}.")
142
+ rows.sort(key=lambda r: (r["date"], r["symbol"]))
143
+ return rows
144
+
145
+
146
+ def split_symbols(symbol: str) -> list[str]:
147
+ """'a, b' -> ['a', 'b']."""
148
+ return [s.strip() for s in symbol.split(",") if s.strip()]
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.5
2
+ Name: openbb-aurastream
3
+ Version: 0.1.0
4
+ Summary: AuraStream data for the OpenBB Platform: EU net short positions, USDA WASDE, FRED vintages, SEC fails-to-deliver and Binance funding rates.
5
+ Project-URL: Homepage, https://aurastream.unbiased-alpha.com
6
+ Project-URL: Documentation, https://aurastream.unbiased-alpha.com/docs/
7
+ Project-URL: Source, https://github.com/UnbiasedAlpha/openbb-aurastream
8
+ Author: Unbiased Alpha
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: aurastream,fred,market data,openbb,quant,short selling,wasde
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: aiohttp>=3.9
14
+ Requires-Dist: openbb-core<3,>=2.0
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest>=8; extra == 'test'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # openbb-aurastream
20
+
21
+ [AuraStream](https://aurastream.unbiased-alpha.com) data for the [OpenBB Platform](https://github.com/OpenBB-finance/OpenBB). The extension adds five commands under `obb.aurastream`. Each covers a dataset that the other OpenBB providers don't offer:
22
+
23
+ | Command | Data |
24
+ |---|---|
25
+ | `net_short` | EU net short positions of 0.5% or more, with the holder's name, from the French (AMF), Dutch (AFM) and Spanish (CNMV) registers, back to 2010–2012 |
26
+ | `wasde` | USDA WASDE projections since July 2010: US balance sheets and world tables by country, one row per monthly report |
27
+ | `fred_vintages` | FRED series with every revision, stamped at the date each value was published |
28
+ | `fails_to_deliver` | SEC fails-to-deliver for any US stock or ETF |
29
+ | `funding_rates` | Binance USD-M perpetual funding rates |
30
+
31
+ ## Install
32
+
33
+ ```
34
+ pip install openbb openbb-aurastream
35
+ ```
36
+
37
+ OpenBB rebuilds its Python interface the first time you import it after installing.
38
+
39
+ ## Credentials
40
+
41
+ You need an AuraStream API key. The free plan includes 500 calls a month, with no credit card required: [sign up here](https://aurastream.unbiased-alpha.com/sign-up).
42
+
43
+ ```python
44
+ from openbb import obb
45
+
46
+ obb.user.credentials.aurastream_api_key = "..."
47
+ # Only for fred_vintages: your own free FRED key (https://fred.stlouisfed.org/docs/api/api_key.html)
48
+ obb.user.credentials.aurastream_fred_api_key = "..."
49
+ # Only for fails_to_deliver: the SEC asks automated requests to name the sender
50
+ obb.user.credentials.aurastream_sec_user_agent = "Jane Doe jane@example.com"
51
+ ```
52
+
53
+ You can also keep them in `~/.openbb_platform/user_settings.json`.
54
+
55
+ ## Examples
56
+
57
+ ```python
58
+ from openbb import obb
59
+
60
+ # Who is short Ubisoft, and by how much
61
+ shorts = obb.aurastream.net_short("ubisoft", start_date="2024-01-01", country="FR").to_df()
62
+ shorts.pivot_table(index=shorts.index.date, columns="holder", values="net_short_pct").ffill()
63
+
64
+ # Every position one fund has disclosed
65
+ obb.aurastream.net_short("all", holder="citadel", start_date="2026-01-01").to_df()
66
+
67
+ # How the USDA's projection of US corn ending stocks changed, report by report
68
+ corn = obb.aurastream.wasde("corn:ending_stocks", start_date="2024-01-01").to_df()
69
+ corn["revision"] = corn["value"] - corn["previous"]
70
+
71
+ # Brazil's soybean crop from the world tables
72
+ obb.aurastream.wasde("soybeans:production:Brazil").to_df()
73
+
74
+ # CPI as first published vs. after revisions
75
+ cpi = obb.aurastream.fred_vintages("CPIAUCSL", start_date="2020-01-01").to_df()
76
+ first_print = cpi.groupby("reference_date")["value"].first()
77
+ latest = cpi.groupby("reference_date")["value"].last()
78
+
79
+ # Fails-to-deliver in GameStop
80
+ obb.aurastream.fails_to_deliver("GME", start_date="2024-01-01", end_date="2025-01-01").to_df()
81
+
82
+ # Funding rates of the Bitcoin and Ether perpetuals
83
+ obb.aurastream.funding_rates("BTCUSDT,ETHUSDT", start_date="2025-06-01").to_df()
84
+ ```
85
+
86
+ Without `start_date`, each command returns the last year.
87
+
88
+ ## Timing
89
+
90
+ All five datasets are historical, not real-time, and each row is stamped when the data became public:
91
+
92
+ - EU net short positions are published by the next trading day, so lag them by one day in a backtest.
93
+ - WASDE rows are stamped at the report's release, 12:00 New York time.
94
+ - For `fred_vintages`, `start_date` and `end_date` filter on the publication date.
95
+ - The SEC publishes fails-to-deliver twice a month, about two weeks after each half-month ends.
96
+
97
+ See the [AuraStream docs](https://aurastream.unbiased-alpha.com/docs/) for the field definitions and coverage of each source.
98
+
99
+ ## Data terms
100
+
101
+ - France: source Autorité des marchés financiers (AMF), data.gouv.fr, Licence Ouverte 2.0.
102
+ - Netherlands: source AFM.
103
+ - Spain: this information can be obtained free of charge from the CNMV website (www.cnmv.es).
104
+ - Holder names can belong to individuals, so treat them as personal data under the GDPR.
105
+
106
+ ## License
107
+
108
+ MIT. This package is by Unbiased Alpha and is not affiliated with OpenBB.
@@ -0,0 +1,15 @@
1
+ openbb_aurastream/__init__.py,sha256=ncT7sOUpQ79J6msgiqz_Txf-2E2DfK_7FWKe9DzkctI,1622
2
+ openbb_aurastream/aurastream_router.py,sha256=TOjQq_Xs14KDBvAVWQAt4XxqwAFExPKwcQVD5SFaIjw,3700
3
+ openbb_aurastream/models/__init__.py,sha256=PTHyuY9uRh3ZtnbI_7kRQZSCDvabO3-MYWc8suSLkB8,25
4
+ openbb_aurastream/models/fails_to_deliver.py,sha256=xk15CRydka3BPIdGL8ek7yk2PGg0yLlGEvKT5YqugGo,3424
5
+ openbb_aurastream/models/fred_vintages.py,sha256=PTXuTomqQDPj6TQkquK7TRuMCopXetcCVsZ8L3jS10I,4068
6
+ openbb_aurastream/models/funding_rates.py,sha256=s6RXyvdo_SDshiIENd8y7mDrwHCG0qFH26j4IAGVpwk,2924
7
+ openbb_aurastream/models/net_short.py,sha256=lACSVBl4QaBBHJFWsRDypIU8oMNTEId7E-QRHPS3l1U,4426
8
+ openbb_aurastream/models/wasde.py,sha256=qQXkXwem_u0Ea-yNnidrGUZ9Bg9VS0JPk4B8CyMY1R8,3521
9
+ openbb_aurastream/utils/__init__.py,sha256=fcXDU1Q3QrZ8YUDBSBmDRyaMWeMcXWp1K00qr0txNPQ,28
10
+ openbb_aurastream/utils/client.py,sha256=Oo5cG_AAhEbQH1nZBTOhpvDdmSnqtkBvWvdmUIPBMIo,5355
11
+ openbb_aurastream-0.1.0.dist-info/METADATA,sha256=DIdERbM4BXX5KTCAi_f3YvZw0Kv6HWxa-utIh4bW010,4650
12
+ openbb_aurastream-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
13
+ openbb_aurastream-0.1.0.dist-info/entry_points.txt,sha256=f_A7WUSp88inDmSAUFWeNiGTGx83MperMel3F6wWsWI,160
14
+ openbb_aurastream-0.1.0.dist-info/licenses/LICENSE,sha256=PXINVVvEqW2nfyyKueaoj_IoKs-luXnuyQ0uef99PKY,1071
15
+ openbb_aurastream-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [openbb_core_extension]
2
+ aurastream = openbb_aurastream.aurastream_router:router
3
+
4
+ [openbb_provider_extension]
5
+ aurastream = openbb_aurastream:aurastream_provider
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Unbiased Alpha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.