openbb-tradingagent 1.0.0__tar.gz
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.
- openbb_tradingagent-1.0.0/PKG-INFO +125 -0
- openbb_tradingagent-1.0.0/README.md +103 -0
- openbb_tradingagent-1.0.0/openbb_tradingagent/__init__.py +34 -0
- openbb_tradingagent-1.0.0/openbb_tradingagent/models/__init__.py +1 -0
- openbb_tradingagent-1.0.0/openbb_tradingagent/models/historical_attributes.py +179 -0
- openbb_tradingagent-1.0.0/openbb_tradingagent/models/latest_attributes.py +140 -0
- openbb_tradingagent-1.0.0/openbb_tradingagent/utils/__init__.py +1 -0
- openbb_tradingagent-1.0.0/openbb_tradingagent/utils/helpers.py +33 -0
- openbb_tradingagent-1.0.0/pyproject.toml +27 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openbb-tradingagent
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Point-in-time company fundamentals from tradingagentapp.com for the OpenBB Platform (US SEC EDGAR, Taiwan TWSE, Korea OpenDART).
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: openbb,fundamentals,point-in-time,edgar,twse,opendart
|
|
7
|
+
Author: Trading Agent
|
|
8
|
+
Author-email: tradingagentapp@gmail.com
|
|
9
|
+
Requires-Python: >=3.10,<4
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: openbb-core (>=1.6.10,<2.0.0)
|
|
18
|
+
Project-URL: Homepage, https://www.tradingagentapp.com/fundamentals
|
|
19
|
+
Project-URL: Repository, https://github.com/Wei-LienWU/trading-agent
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Trading Agent provider for OpenBB
|
|
23
|
+
|
|
24
|
+
Point-in-time, **as-first-reported** company fundamentals from official regulator
|
|
25
|
+
filings — inside the OpenBB Platform.
|
|
26
|
+
|
|
27
|
+
| Market | Source | Coverage |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| United States | SEC EDGAR (XBRL) | ~7,000 filers, annual + quarterly, FY2014→ |
|
|
30
|
+
| Taiwan | Taiwan Stock Exchange OpenAPI | every listed company |
|
|
31
|
+
| Korea | FSS OpenDART | loading (full market) |
|
|
32
|
+
|
|
33
|
+
Every value is keyed by the **official filing date it became public on**, and
|
|
34
|
+
queries accept an `as_of` knowledge date — so a backtest at date *T* sees only
|
|
35
|
+
what was public at *T*. No restatement look-ahead.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install openbb-tradingagent
|
|
41
|
+
openbb-build # regenerate the static assets so the provider appears
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Use
|
|
45
|
+
|
|
46
|
+
The provider attaches to OpenBB's existing fundamentals commands:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from openbb import obb
|
|
50
|
+
|
|
51
|
+
# No key needed — the demo tier covers 25 flagship tickers at full fidelity.
|
|
52
|
+
obb.equity.fundamental.latest_attributes(
|
|
53
|
+
symbol="2330.TW", tag="revenue,net_income", provider="tradingagent"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Point-in-time: what did AAPL's reported revenue history look like on 2022-01-01?
|
|
57
|
+
obb.equity.fundamental.historical_attributes(
|
|
58
|
+
symbol="AAPL", tag="revenue", provider="tradingagent", as_of="2022-01-01"
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Set a key for the full universe:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
obb.user.credentials.tradingagent_api_key = "ta_live_…"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
or in `~/.openbb_platform/user_settings.json`:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{ "credentials": { "tradingagent_api_key": "ta_live_…" } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Without a key the provider falls back to the public `demo` key: 25 flagship
|
|
75
|
+
tickers (US, Taiwan, Korea), full history, all fields, no signup. Paid plans
|
|
76
|
+
unlock every ticker — your code does not change.
|
|
77
|
+
|
|
78
|
+
## The `as_of` parameter
|
|
79
|
+
|
|
80
|
+
This is the reason the provider exists. Most fundamentals APIs serve *today's*
|
|
81
|
+
restated figures against *historical* dates, which quietly leaks future
|
|
82
|
+
information into a backtest. Here, `as_of` filters to `filed <= as_of`:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# FY2021 as it was actually reported, not as later amended
|
|
86
|
+
obb.equity.fundamental.latest_attributes(
|
|
87
|
+
symbol="AAPL", tag="revenue", provider="tradingagent", as_of="2022-01-01"
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Fields
|
|
92
|
+
|
|
93
|
+
`historical_attributes` returns, per row: `date` (period end), `symbol`, `tag`,
|
|
94
|
+
`value`, `unit`, `fiscal_year`, `fiscal_period`, `filed`, `form`, `market`,
|
|
95
|
+
`issuer_id`.
|
|
96
|
+
|
|
97
|
+
Available tags include `revenue`, `net_income`, `assets`, `liabilities`,
|
|
98
|
+
`equity`, `cash`, `gross_profit`, `operating_income`, `long_term_debt`,
|
|
99
|
+
`rnd_expense`, `eps_diluted`, `shares_diluted`, plus derived ratios
|
|
100
|
+
`net_margin`, `gross_margin`, `operating_margin`, `roe`, `roa`,
|
|
101
|
+
`debt_to_equity`, `asset_turnover`.
|
|
102
|
+
|
|
103
|
+
## Data notes
|
|
104
|
+
|
|
105
|
+
- **Taiwan income-statement figures are cumulative year-to-date** (Taiwan
|
|
106
|
+
reporting convention): Q2 is the H1 total, Q4 the full year. Difference
|
|
107
|
+
adjacent quarters for single-quarter flows. Balance-sheet items are
|
|
108
|
+
point-in-time as usual.
|
|
109
|
+
- Values are as filed. Filers make mistakes; we do not silently correct them.
|
|
110
|
+
|
|
111
|
+
## Sources and licences
|
|
112
|
+
|
|
113
|
+
- **US** — SEC EDGAR, public domain.
|
|
114
|
+
- **Taiwan** — Taiwan Stock Exchange, under the Open Government Data License 1.0.
|
|
115
|
+
The attribution is embedded in every API response and must be preserved.
|
|
116
|
+
- **Korea** — FSS OpenDART. Public data; the FSS confirmed in writing that no
|
|
117
|
+
separate approval agreement is required for commercial use.
|
|
118
|
+
|
|
119
|
+
Factual historical data only — not investment advice, not a recommendation.
|
|
120
|
+
|
|
121
|
+
Full API documentation: <https://www.tradingagentapp.com/developers>
|
|
122
|
+
Pricing and licences: <https://www.tradingagentapp.com/fundamentals>
|
|
123
|
+
|
|
124
|
+
MIT licensed. Operated by WU Capital Limited, Auckland, New Zealand.
|
|
125
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Trading Agent provider for OpenBB
|
|
2
|
+
|
|
3
|
+
Point-in-time, **as-first-reported** company fundamentals from official regulator
|
|
4
|
+
filings — inside the OpenBB Platform.
|
|
5
|
+
|
|
6
|
+
| Market | Source | Coverage |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| United States | SEC EDGAR (XBRL) | ~7,000 filers, annual + quarterly, FY2014→ |
|
|
9
|
+
| Taiwan | Taiwan Stock Exchange OpenAPI | every listed company |
|
|
10
|
+
| Korea | FSS OpenDART | loading (full market) |
|
|
11
|
+
|
|
12
|
+
Every value is keyed by the **official filing date it became public on**, and
|
|
13
|
+
queries accept an `as_of` knowledge date — so a backtest at date *T* sees only
|
|
14
|
+
what was public at *T*. No restatement look-ahead.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install openbb-tradingagent
|
|
20
|
+
openbb-build # regenerate the static assets so the provider appears
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Use
|
|
24
|
+
|
|
25
|
+
The provider attaches to OpenBB's existing fundamentals commands:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from openbb import obb
|
|
29
|
+
|
|
30
|
+
# No key needed — the demo tier covers 25 flagship tickers at full fidelity.
|
|
31
|
+
obb.equity.fundamental.latest_attributes(
|
|
32
|
+
symbol="2330.TW", tag="revenue,net_income", provider="tradingagent"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Point-in-time: what did AAPL's reported revenue history look like on 2022-01-01?
|
|
36
|
+
obb.equity.fundamental.historical_attributes(
|
|
37
|
+
symbol="AAPL", tag="revenue", provider="tradingagent", as_of="2022-01-01"
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Set a key for the full universe:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
obb.user.credentials.tradingagent_api_key = "ta_live_…"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
or in `~/.openbb_platform/user_settings.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{ "credentials": { "tradingagent_api_key": "ta_live_…" } }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Without a key the provider falls back to the public `demo` key: 25 flagship
|
|
54
|
+
tickers (US, Taiwan, Korea), full history, all fields, no signup. Paid plans
|
|
55
|
+
unlock every ticker — your code does not change.
|
|
56
|
+
|
|
57
|
+
## The `as_of` parameter
|
|
58
|
+
|
|
59
|
+
This is the reason the provider exists. Most fundamentals APIs serve *today's*
|
|
60
|
+
restated figures against *historical* dates, which quietly leaks future
|
|
61
|
+
information into a backtest. Here, `as_of` filters to `filed <= as_of`:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# FY2021 as it was actually reported, not as later amended
|
|
65
|
+
obb.equity.fundamental.latest_attributes(
|
|
66
|
+
symbol="AAPL", tag="revenue", provider="tradingagent", as_of="2022-01-01"
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Fields
|
|
71
|
+
|
|
72
|
+
`historical_attributes` returns, per row: `date` (period end), `symbol`, `tag`,
|
|
73
|
+
`value`, `unit`, `fiscal_year`, `fiscal_period`, `filed`, `form`, `market`,
|
|
74
|
+
`issuer_id`.
|
|
75
|
+
|
|
76
|
+
Available tags include `revenue`, `net_income`, `assets`, `liabilities`,
|
|
77
|
+
`equity`, `cash`, `gross_profit`, `operating_income`, `long_term_debt`,
|
|
78
|
+
`rnd_expense`, `eps_diluted`, `shares_diluted`, plus derived ratios
|
|
79
|
+
`net_margin`, `gross_margin`, `operating_margin`, `roe`, `roa`,
|
|
80
|
+
`debt_to_equity`, `asset_turnover`.
|
|
81
|
+
|
|
82
|
+
## Data notes
|
|
83
|
+
|
|
84
|
+
- **Taiwan income-statement figures are cumulative year-to-date** (Taiwan
|
|
85
|
+
reporting convention): Q2 is the H1 total, Q4 the full year. Difference
|
|
86
|
+
adjacent quarters for single-quarter flows. Balance-sheet items are
|
|
87
|
+
point-in-time as usual.
|
|
88
|
+
- Values are as filed. Filers make mistakes; we do not silently correct them.
|
|
89
|
+
|
|
90
|
+
## Sources and licences
|
|
91
|
+
|
|
92
|
+
- **US** — SEC EDGAR, public domain.
|
|
93
|
+
- **Taiwan** — Taiwan Stock Exchange, under the Open Government Data License 1.0.
|
|
94
|
+
The attribution is embedded in every API response and must be preserved.
|
|
95
|
+
- **Korea** — FSS OpenDART. Public data; the FSS confirmed in writing that no
|
|
96
|
+
separate approval agreement is required for commercial use.
|
|
97
|
+
|
|
98
|
+
Factual historical data only — not investment advice, not a recommendation.
|
|
99
|
+
|
|
100
|
+
Full API documentation: <https://www.tradingagentapp.com/developers>
|
|
101
|
+
Pricing and licences: <https://www.tradingagentapp.com/fundamentals>
|
|
102
|
+
|
|
103
|
+
MIT licensed. Operated by WU Capital Limited, Auckland, New Zealand.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Trading Agent provider for the OpenBB Platform.
|
|
2
|
+
|
|
3
|
+
Point-in-time, as-first-reported company fundamentals from official
|
|
4
|
+
regulator filings (US SEC EDGAR, Taiwan TWSE, Korea FSS OpenDART).
|
|
5
|
+
Every row carries its official filing date; queries can be pinned to an
|
|
6
|
+
`as_of` knowledge date so a backtest at date T sees only what was public
|
|
7
|
+
at T — no restatement look-ahead.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from openbb_core.provider.abstract.provider import Provider
|
|
11
|
+
|
|
12
|
+
from openbb_tradingagent.models.historical_attributes import (
|
|
13
|
+
TradingAgentHistoricalAttributesFetcher,
|
|
14
|
+
)
|
|
15
|
+
from openbb_tradingagent.models.latest_attributes import (
|
|
16
|
+
TradingAgentLatestAttributesFetcher,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
tradingagent_provider = Provider(
|
|
20
|
+
name="tradingagent",
|
|
21
|
+
website="https://www.tradingagentapp.com",
|
|
22
|
+
description=(
|
|
23
|
+
"Point-in-time company fundamentals from official regulator filings "
|
|
24
|
+
"(US SEC EDGAR, Taiwan TWSE, Korea FSS OpenDART). As-first-reported "
|
|
25
|
+
"values keyed by filing date, with an `as_of` parameter for "
|
|
26
|
+
"look-ahead-free backtests. Works without a key on 25 flagship "
|
|
27
|
+
"tickers (demo tier); paid keys unlock the full universe."
|
|
28
|
+
),
|
|
29
|
+
credentials=["api_key"],
|
|
30
|
+
fetcher_dict={
|
|
31
|
+
"LatestAttributes": TradingAgentLatestAttributesFetcher,
|
|
32
|
+
"HistoricalAttributes": TradingAgentHistoricalAttributesFetcher,
|
|
33
|
+
},
|
|
34
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Trading Agent provider models."""
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Trading Agent Historical Attributes Model."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=unused-argument
|
|
4
|
+
|
|
5
|
+
from datetime import date as dateType
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
|
9
|
+
from openbb_core.provider.standard_models.historical_attributes import (
|
|
10
|
+
HistoricalAttributesData,
|
|
11
|
+
HistoricalAttributesQueryParams,
|
|
12
|
+
)
|
|
13
|
+
from openbb_core.provider.utils.errors import EmptyDataError
|
|
14
|
+
from pydantic import Field, field_validator
|
|
15
|
+
|
|
16
|
+
from openbb_tradingagent.utils.helpers import get_data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TradingAgentHistoricalAttributesQueryParams(HistoricalAttributesQueryParams):
|
|
20
|
+
"""Trading Agent Historical Attributes Query.
|
|
21
|
+
|
|
22
|
+
Source: https://www.tradingagentapp.com/developers#fundamentals
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
as_of: Optional[dateType] = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description=(
|
|
28
|
+
"Point-in-time knowledge date. Only facts whose official filing date"
|
|
29
|
+
" is on or before this date are returned, so a backtest at date T"
|
|
30
|
+
" sees exactly what was public at T (no restatement look-ahead)."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TradingAgentHistoricalAttributesData(HistoricalAttributesData):
|
|
36
|
+
"""Trading Agent Historical Attributes Data.
|
|
37
|
+
|
|
38
|
+
One as-first-reported fact: a metric for one fiscal period, keyed by the
|
|
39
|
+
official filing date it became public on.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# All source-key mappings live here; do not use Field(alias=...) — the
|
|
43
|
+
# base Data model installs a camelCase alias generator that would win.
|
|
44
|
+
__alias_dict__ = {
|
|
45
|
+
"date": "end",
|
|
46
|
+
"symbol": "t",
|
|
47
|
+
"tag": "m",
|
|
48
|
+
"value": "v",
|
|
49
|
+
"unit": "u",
|
|
50
|
+
"fiscal_year": "fy",
|
|
51
|
+
"fiscal_period": "fp",
|
|
52
|
+
"market": "mkt",
|
|
53
|
+
"issuer_id": "c",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
unit: Optional[str] = Field(
|
|
57
|
+
default=None,
|
|
58
|
+
description="Unit of measure (USD, shares, USD/shares, ratio, …).",
|
|
59
|
+
)
|
|
60
|
+
fiscal_year: Optional[int] = Field(
|
|
61
|
+
default=None,
|
|
62
|
+
description="Fiscal year of the period end.",
|
|
63
|
+
)
|
|
64
|
+
fiscal_period: Optional[str] = Field(
|
|
65
|
+
default=None,
|
|
66
|
+
description="Fiscal period (FY, Q1, Q2, Q3, Q4).",
|
|
67
|
+
)
|
|
68
|
+
filed: Optional[dateType] = Field(
|
|
69
|
+
default=None,
|
|
70
|
+
description="Official filing date — the point-in-time key.",
|
|
71
|
+
)
|
|
72
|
+
form: Optional[str] = Field(
|
|
73
|
+
default=None,
|
|
74
|
+
description="Source filing form type, where the regulator publishes one.",
|
|
75
|
+
)
|
|
76
|
+
market: Optional[str] = Field(
|
|
77
|
+
default=None,
|
|
78
|
+
description="Market the issuer is listed in (US, TW, KR, …).",
|
|
79
|
+
)
|
|
80
|
+
issuer_id: Optional[str] = Field(
|
|
81
|
+
default=None,
|
|
82
|
+
description="Regulator's own issuer identifier (SEC CIK, TWSE/DART code).",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@field_validator("value", mode="before", check_fields=False)
|
|
86
|
+
@classmethod
|
|
87
|
+
def validate_value(cls, v):
|
|
88
|
+
"""Coerce numeric strings; drop anything non-numeric."""
|
|
89
|
+
if v is None or isinstance(v, float):
|
|
90
|
+
return v
|
|
91
|
+
try:
|
|
92
|
+
return float(v)
|
|
93
|
+
except (TypeError, ValueError):
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TradingAgentHistoricalAttributesFetcher(
|
|
98
|
+
Fetcher[
|
|
99
|
+
TradingAgentHistoricalAttributesQueryParams,
|
|
100
|
+
List[TradingAgentHistoricalAttributesData],
|
|
101
|
+
]
|
|
102
|
+
):
|
|
103
|
+
"""Trading Agent Historical Attributes Fetcher."""
|
|
104
|
+
|
|
105
|
+
# The public `demo` key covers 25 flagship tickers at full fidelity, so the
|
|
106
|
+
# provider is usable before signup. Leaving this True would make OpenBB
|
|
107
|
+
# refuse the call outright when no key is configured.
|
|
108
|
+
require_credentials = False
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def transform_query(
|
|
112
|
+
params: Dict[str, Any]
|
|
113
|
+
) -> TradingAgentHistoricalAttributesQueryParams:
|
|
114
|
+
"""Transform the query params."""
|
|
115
|
+
return TradingAgentHistoricalAttributesQueryParams(**params)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
async def aextract_data(
|
|
119
|
+
query: TradingAgentHistoricalAttributesQueryParams,
|
|
120
|
+
credentials: Optional[Dict[str, str]],
|
|
121
|
+
**kwargs: Any,
|
|
122
|
+
) -> List[Dict]:
|
|
123
|
+
"""Extract the raw rows, one request per symbol."""
|
|
124
|
+
api_key = (credentials or {}).get("tradingagent_api_key")
|
|
125
|
+
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
|
126
|
+
|
|
127
|
+
results: List[Dict] = []
|
|
128
|
+
for symbol in symbols:
|
|
129
|
+
response = await get_data(
|
|
130
|
+
"/fundamentals",
|
|
131
|
+
{
|
|
132
|
+
"ticker": symbol,
|
|
133
|
+
"as_of": query.as_of.isoformat() if query.as_of else None,
|
|
134
|
+
"from": query.start_date.isoformat() if query.start_date else None,
|
|
135
|
+
},
|
|
136
|
+
api_key,
|
|
137
|
+
**kwargs,
|
|
138
|
+
)
|
|
139
|
+
rows = response.get("data", []) if isinstance(response, dict) else []
|
|
140
|
+
# The API returns the ticker on the envelope, not on every row.
|
|
141
|
+
for row in rows:
|
|
142
|
+
row.setdefault("t", response.get("ticker", symbol))
|
|
143
|
+
results.extend(rows)
|
|
144
|
+
|
|
145
|
+
if not results:
|
|
146
|
+
raise EmptyDataError(
|
|
147
|
+
f"No fundamentals returned for {query.symbol}"
|
|
148
|
+
+ (f" as of {query.as_of}" if query.as_of else "")
|
|
149
|
+
+ ". Either the ticker is outside the covered universe, or no"
|
|
150
|
+
" filing was public by that date — each market's history depth"
|
|
151
|
+
" is listed at https://www.tradingagentapp.com/api/v1/fundamentals"
|
|
152
|
+
)
|
|
153
|
+
return results
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def transform_data(
|
|
157
|
+
query: TradingAgentHistoricalAttributesQueryParams,
|
|
158
|
+
data: List[Dict],
|
|
159
|
+
**kwargs: Any,
|
|
160
|
+
) -> List[TradingAgentHistoricalAttributesData]:
|
|
161
|
+
"""Filter to the requested tags and sort."""
|
|
162
|
+
tags = {t.strip().lower() for t in query.tag.split(",") if t.strip()}
|
|
163
|
+
rows = [r for r in data if not tags or str(r.get("m", "")).lower() in tags]
|
|
164
|
+
|
|
165
|
+
if query.end_date:
|
|
166
|
+
rows = [r for r in rows if str(r.get("end", "")) <= query.end_date.isoformat()]
|
|
167
|
+
|
|
168
|
+
rows.sort(key=lambda r: str(r.get("end", "")), reverse=query.sort == "desc")
|
|
169
|
+
if query.limit:
|
|
170
|
+
rows = rows[: query.limit]
|
|
171
|
+
|
|
172
|
+
if not rows:
|
|
173
|
+
raise EmptyDataError(
|
|
174
|
+
f"No rows for tag(s) '{query.tag}'. Discover the available metric"
|
|
175
|
+
" names from the API index at /api/v1/fundamentals."
|
|
176
|
+
)
|
|
177
|
+
return [
|
|
178
|
+
TradingAgentHistoricalAttributesData.model_validate(r) for r in rows
|
|
179
|
+
]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Trading Agent Latest Attributes Model."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=unused-argument
|
|
4
|
+
|
|
5
|
+
from datetime import date as dateType
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
|
9
|
+
from openbb_core.provider.standard_models.latest_attributes import (
|
|
10
|
+
LatestAttributesData,
|
|
11
|
+
LatestAttributesQueryParams,
|
|
12
|
+
)
|
|
13
|
+
from openbb_core.provider.utils.errors import EmptyDataError
|
|
14
|
+
from pydantic import Field
|
|
15
|
+
|
|
16
|
+
from openbb_tradingagent.utils.helpers import get_data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TradingAgentLatestAttributesQueryParams(LatestAttributesQueryParams):
|
|
20
|
+
"""Trading Agent Latest Attributes Query.
|
|
21
|
+
|
|
22
|
+
Source: https://www.tradingagentapp.com/developers#fundamentals
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
as_of: Optional[dateType] = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description=(
|
|
28
|
+
"Point-in-time knowledge date. Returns the latest value per metric"
|
|
29
|
+
" that was already public on this date — 'latest' as of then, not"
|
|
30
|
+
" today's restated figure. Defaults to today."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TradingAgentLatestAttributesData(LatestAttributesData):
|
|
36
|
+
"""Trading Agent Latest Attributes Data.
|
|
37
|
+
|
|
38
|
+
The most recent as-first-reported value per metric, with the fiscal period
|
|
39
|
+
and filing date that produced it.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
unit: Optional[str] = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="Unit of measure (USD, shares, USD/shares, ratio, …).",
|
|
45
|
+
)
|
|
46
|
+
fiscal_year: Optional[int] = Field(
|
|
47
|
+
default=None, description="Fiscal year of the period end."
|
|
48
|
+
)
|
|
49
|
+
period_ending: Optional[dateType] = Field(
|
|
50
|
+
default=None, description="Period end date of the reported value."
|
|
51
|
+
)
|
|
52
|
+
filed: Optional[dateType] = Field(
|
|
53
|
+
default=None,
|
|
54
|
+
description="Official filing date — the point-in-time key.",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TradingAgentLatestAttributesFetcher(
|
|
59
|
+
Fetcher[
|
|
60
|
+
TradingAgentLatestAttributesQueryParams,
|
|
61
|
+
List[TradingAgentLatestAttributesData],
|
|
62
|
+
]
|
|
63
|
+
):
|
|
64
|
+
"""Trading Agent Latest Attributes Fetcher."""
|
|
65
|
+
|
|
66
|
+
# The public `demo` key covers 25 flagship tickers at full fidelity, so the
|
|
67
|
+
# provider is usable before signup. Leaving this True would make OpenBB
|
|
68
|
+
# refuse the call outright when no key is configured.
|
|
69
|
+
require_credentials = False
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def transform_query(
|
|
73
|
+
params: Dict[str, Any]
|
|
74
|
+
) -> TradingAgentLatestAttributesQueryParams:
|
|
75
|
+
"""Transform the query params."""
|
|
76
|
+
return TradingAgentLatestAttributesQueryParams(**params)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
async def aextract_data(
|
|
80
|
+
query: TradingAgentLatestAttributesQueryParams,
|
|
81
|
+
credentials: Optional[Dict[str, str]],
|
|
82
|
+
**kwargs: Any,
|
|
83
|
+
) -> List[Dict]:
|
|
84
|
+
"""Extract the as-of snapshot and flatten it to one row per metric."""
|
|
85
|
+
api_key = (credentials or {}).get("tradingagent_api_key")
|
|
86
|
+
symbols = [s.strip().upper() for s in query.symbol.split(",") if s.strip()]
|
|
87
|
+
|
|
88
|
+
results: List[Dict] = []
|
|
89
|
+
for symbol in symbols:
|
|
90
|
+
response = await get_data(
|
|
91
|
+
"/fundamentals",
|
|
92
|
+
{
|
|
93
|
+
"ticker": symbol,
|
|
94
|
+
"view": "latest",
|
|
95
|
+
"as_of": query.as_of.isoformat() if query.as_of else None,
|
|
96
|
+
},
|
|
97
|
+
api_key,
|
|
98
|
+
**kwargs,
|
|
99
|
+
)
|
|
100
|
+
snapshot = response.get("data", {}) if isinstance(response, dict) else {}
|
|
101
|
+
for metric, fact in snapshot.items():
|
|
102
|
+
results.append(
|
|
103
|
+
{
|
|
104
|
+
"symbol": response.get("ticker", symbol),
|
|
105
|
+
"tag": metric,
|
|
106
|
+
"value": fact.get("value"),
|
|
107
|
+
"unit": fact.get("unit"),
|
|
108
|
+
"fiscal_year": fact.get("fy"),
|
|
109
|
+
"period_ending": fact.get("end"),
|
|
110
|
+
"filed": fact.get("filed"),
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if not results:
|
|
115
|
+
raise EmptyDataError(
|
|
116
|
+
f"No fundamentals returned for {query.symbol}"
|
|
117
|
+
+ (f" as of {query.as_of}" if query.as_of else "")
|
|
118
|
+
+ ". Either the ticker is outside the covered universe, or no"
|
|
119
|
+
" filing was public by that date — each market's history depth"
|
|
120
|
+
" is listed at https://www.tradingagentapp.com/api/v1/fundamentals"
|
|
121
|
+
)
|
|
122
|
+
return results
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def transform_data(
|
|
126
|
+
query: TradingAgentLatestAttributesQueryParams,
|
|
127
|
+
data: List[Dict],
|
|
128
|
+
**kwargs: Any,
|
|
129
|
+
) -> List[TradingAgentLatestAttributesData]:
|
|
130
|
+
"""Filter to the requested tags."""
|
|
131
|
+
tags = {t.strip().lower() for t in query.tag.split(",") if t.strip()}
|
|
132
|
+
rows = [r for r in data if not tags or str(r["tag"]).lower() in tags]
|
|
133
|
+
|
|
134
|
+
if not rows:
|
|
135
|
+
available = sorted({str(r["tag"]) for r in data})
|
|
136
|
+
raise EmptyDataError(
|
|
137
|
+
f"No values for tag(s) '{query.tag}'. Available for this ticker: "
|
|
138
|
+
+ ", ".join(available)
|
|
139
|
+
)
|
|
140
|
+
return [TradingAgentLatestAttributesData.model_validate(r) for r in rows]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Trading Agent provider utilities."""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Trading Agent provider helpers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from openbb_core.provider.utils.helpers import amake_request
|
|
7
|
+
|
|
8
|
+
BASE_URL = os.environ.get(
|
|
9
|
+
"TRADINGAGENT_BASE_URL", "https://www.tradingagentapp.com/api/v1"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def get_data(
|
|
14
|
+
path: str,
|
|
15
|
+
params: Dict[str, Any],
|
|
16
|
+
api_key: Optional[str],
|
|
17
|
+
**kwargs: Any,
|
|
18
|
+
) -> Any:
|
|
19
|
+
"""GET a Trading Agent API path with Bearer auth; None params are dropped.
|
|
20
|
+
|
|
21
|
+
Falls back to the public `demo` key (25 flagship tickers, full history)
|
|
22
|
+
when no key is configured.
|
|
23
|
+
"""
|
|
24
|
+
query = "&".join(f"{k}={v}" for k, v in params.items() if v is not None)
|
|
25
|
+
url = f"{BASE_URL}{path}" + (f"?{query}" if query else "")
|
|
26
|
+
headers = {"Authorization": f"Bearer {api_key or 'demo'}"}
|
|
27
|
+
response = await amake_request(url, headers=headers, **kwargs)
|
|
28
|
+
if isinstance(response, dict) and response.get("error"):
|
|
29
|
+
raise RuntimeError(
|
|
30
|
+
f"Trading Agent API error '{response['error']}': "
|
|
31
|
+
f"{response.get('message', '')}"
|
|
32
|
+
)
|
|
33
|
+
return response
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "openbb-tradingagent"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Point-in-time company fundamentals from tradingagentapp.com for the OpenBB Platform (US SEC EDGAR, Taiwan TWSE, Korea OpenDART)."
|
|
5
|
+
authors = ["Trading Agent <tradingagentapp@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://www.tradingagentapp.com/fundamentals"
|
|
9
|
+
repository = "https://github.com/Wei-LienWU/trading-agent"
|
|
10
|
+
keywords = ["openbb", "fundamentals", "point-in-time", "edgar", "twse", "opendart"]
|
|
11
|
+
packages = [{ include = "openbb_tradingagent" }]
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dependencies]
|
|
14
|
+
python = ">=3.10,<4"
|
|
15
|
+
openbb-core = "^1.6.10"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["poetry-core"]
|
|
19
|
+
build-backend = "poetry.core.masonry.api"
|
|
20
|
+
|
|
21
|
+
[tool.poetry.plugins."openbb_provider_extension"]
|
|
22
|
+
tradingagent = "openbb_tradingagent:tradingagent_provider"
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
markers = [
|
|
26
|
+
"record_http: OpenBB's VCR marker — replays cassettes inside the OpenBB repo, hits the live API standalone.",
|
|
27
|
+
]
|