Ctrl K
ComunidadGuías…

cli/CODEGEN_EODHD_DEMO.md

Rama develop · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# `openbb-cli` codegen + EODHD data exploration

## Part 1 — Codegen: OpenAPI spec → installable OpenBB extension

`openbb --generate-spec` snapshots any OpenAPI 3.x document into a `.spec` file; `--generate-extension`
turns that `.spec` into a full `Provider` + `Fetcher` + `Router` package that registers with
`openbb-build` like any first-party extension. Worked example against the NY Fed Markets API,
which publishes its OpenAPI document at a non-default path:

```bash
openbb --generate-spec \
       --server https://markets.newyorkfed.org \
       --openapi-path /static/docs/markets-api.yml \
       --output nyfed.spec
# wrote 53 commands to nyfed.spec
```

### What's inside a `.spec` file

It's a self-contained JSON snapshot: `base_url`, every command's parameters, and full
`response_schema` per content type (including the upstream's XML shape, when it has one). Real
excerpt for `ambs.latest`:

```json
{
  "base_url": "https://markets.newyorkfed.org",
  "api_prefix": "/api",
  "commands": {
    "ambs.latest": {
      "url_path": "/api/ambs/{operation}/{status}/{include}/latest.{format}",
      "method": "get",
      "description": "Returns the latest AMBS operation Announcements or Results for the current day.",
      "parameters": [
        {
          "name": "operation",
          "in": "path",
          "type": "string",
          "required": true,
          "choices": ["all", "purchases", "sales", "roll", "swap"],
          "help": "The operation type"
        },
        {
          "name": "status",
          "in": "path",
          "type": "string",
          "required": true,
          "choices": ["announcements", "results"],
          "help": "The operation status"
        }
      ]
    }
  }
}
```

Because it's precomputed, dispatching against `--spec nyfed.spec` skips the OpenAPI fetch + parse
on every call (~50ms cold start vs. a live `--server` fetch).

### Generate the extension project

```bash
openbb --generate-extension \
       --spec nyfed.spec \
       --provider-name nyfed \
       --output ./openbb-nyfed
# wrote project to openbb-nyfed/openbb-nyfed
#   providers (1): nyfed
#   routers (1): nyfed
#   fetchers: 53 GET (across all providers)
#   POST:     0 local-compute commands
#   install:  pip install -e openbb-nyfed/openbb-nyfed
#   build:    openbb-build
```

```
openbb-nyfed/openbb-nyfed/
├── pyproject.toml                       # entry points: openbb_provider_extension, openbb_core_extension
├── README.md
└── openbb_nyfed/
    ├── providers/nyfed/
    │   ├── __init__.py                  # nyfed_provider = Provider(name="nyfed", fetcher_dict={...})
    │   └── models/ambs_latest.py        # QueryParams + Data + Fetcher, one file per command
    └── routers/
        ├── nyfed.py                     # root router, include_router() per sub-namespace
        ├── ambs.py
        ├── rates.py
        ├── soma.py
        └── ...                          # one file per top-level path segment
```

```bash
pip install -e ./openbb-nyfed/openbb-nyfed
openbb-build
python -c "from openbb import obb; print(obb.nyfed.rates.all.latest().to_df())"
```

### Codegen against EODHD — a second real API, warts included

EODHD publishes its own OpenAPI 3.1 document (not at the default `/openapi.json`), which is a good
stress test for `--openapi-path` accepting a fully-qualified URL independent of `--server`:

```bash
openbb --generate-spec \
       --server https://eodhd.com/api \
       --openapi-path https://eodhistoricaldata.github.io/EODHD-openapi/openapi.yaml \
       --output eodhd.spec
# wrote 87 commands to eodhd.spec
```

```bash
openbb --generate-extension --spec eodhd.spec --provider-name eodhd --output ./openbb-eodhd
```

```
E741 Ambiguous variable name: `l`
  --> openbb-eodhd/openbb-eodhd/openbb_eodhd/providers/eodhd/models/history.py:77:5
   |
77 |     l: float | None = Field(default=None, description='Low price.')

wrote project to openbb-eodhd/openbb-eodhd
  providers (1): eodhd
  routers (1): eodhd
  fetchers: 87 GET (across all providers)
  POST:     0 local-compute commands
  install:  pip install -e openbb-eodhd/openbb-eodhd
  build:    openbb-build
```

That ruff warning is genuine: the generator maps a `low` field literally named `l` in the OHLC
schema and doesn't rename single-letter identifiers. Codegen output is a starting point, not
guaranteed lint-clean — worth a pass before shipping the package.

Two things worth inspecting in the generated code:

**Credential auto-detection** — EODHD's `api_token` query param was recognized as a credential and
promoted onto the provider, so once installed, `openbb` reads it from user settings instead of
requiring a flag on every call:

```python
# openbb_eodhd/providers/eodhd/__init__.py
eodhd_provider = Provider(
    name="eodhd",
    description="eodhd provider proxied through https://eodhd.com/api.",
    credentials=["api_token"],
    website="https://eodhd.com/api",
    fetcher_dict={
        "History": HistoryFetcher,
        "BulkFundamentals": BulkFundamentalsFetcher,
        "Eod": EodFetcher,
        # ... 84 more
    },
)
```

**Typed router command** — every command becomes a real `@router.command`, so `obb.eodhd.eod(...)`
gets full auto-completion and an `OBBject` envelope, same as any first-party command:

```python
# openbb_eodhd/routers/eodhd.py
@router.command(model="Eod")
async def eod(
    cc: CommandContext,
    provider_choices: ProviderChoices,
    standard_params: StandardParams,
    extra_params: ExtraParams,
) -> OBBject:
    """Retrieve end-of-day historical data for a specific company by its ticker symbol, with optional filters for specific data points."""
    return await OBBject.from_query(Query(**locals()))
```

**Credential naming gotcha** — if you already have OpenBB's legacy EODHD credential saved
(`eodhd_api_key`, from the old first-party provider), it will **not** be picked up here. The
generated Fetcher reads `credentials.get("eodhd_api_token", "")` — matching the provider name
(`eodhd`) plus the exact query param the live spec declares (`api_token`, not `api_key`).
Add the matching key to `~/.openbb_platform/user_settings.json`:

```json
{
  "credentials": {
    "eodhd_api_key": "<existing legacy key>",
    "eodhd_api_token": "<same value — this is the key the generated Fetcher actually reads>"
  }
}
```

With that in place, install + build and it passes through with no key on the call site:

```bash
pip install -e ./openbb-eodhd/openbb-eodhd
openbb-build
```

```
Extensions to add: eodhd@0.1.0
Building...
```

```python
from openbb import obb

obb.eodhd.eod(ticker="MSFT.US", from_="2024-01-01", to="2024-01-10").to_df()
```

```
              open    high     low   close  adjusted_close    volume
date
2024-01-02  373.86  375.90  366.77  370.87        363.1179  25258600
2024-01-03  369.01  373.26  368.51  370.60        362.8536  23083500
2024-01-04  370.67  373.10  367.17  367.94        360.2492  20901500
2024-01-05  368.97  372.06  366.50  367.75        360.0632  21004600
2024-01-08  369.30  375.20  369.01  374.69        366.8581  23134000
2024-01-09  372.01  375.99  371.19  375.79        367.9351  20830000
2024-01-10  376.37  384.17  376.32  382.77        374.7692  25514200
```

This is `MSFT.US` — not `AAPL.US`/`VTI.US` from the demo key's allowlist — confirming the real key is
what's actually being used, resolved automatically from user settings. `search`, which returned
`HTTP403` under the restricted demo key in Part 2 below, also succeeds now:

```python
obb.eodhd.search(query="Microsoft").to_df().head(3)
```

```
        Code Exchange  ...  previousCloseDate  isPrimary
0       MSFT       US  ...          2026-09-24       True
1       MSFT       BA  ...          2026-09-24      False
2  MSFTX-USD       CC  ...          2026-09-25      False
```

## Part 2 — Data exploration against EODHD (spec-driven, no install required)

Everything in this section runs straight off `eodhd.spec` — no extension install, no `openbb-build`.
This is the fast path for exploring an API before deciding it's worth turning into a permanent
extension.

### Discover the surface

```bash
openbb --spec eodhd.spec --list-commands
```

87 commands span EOD prices, fundamentals, calendar events, options, sanctions lists, credit-risk
indices, and more:

```json
{"name": "eod", "description": "Retrieve end-of-day historical data for a specific company by its ticker symbol, with optional filters for specific data points"}
{"name": "real-time", "description": "Retrieve live (delayed) stock prices for specified ticker(s)"}
{"name": "calendar.earnings", "description": "Fetches upcoming earnings data, with optional parameters for date ranges and specific symbols"}
{"name": "fundamentals", "description": "Fetches various sections of fundamental data for a given stock symbol"}
{"name": "screener", "description": "Retrieves stock data filtered by criteria such as market capitalization, exchange, sector, and more"}
{"name": "sanctions.entities", "description": "Returns sanctioned entities (individuals and organizations) aggregated from OFAC and other sanctions sources"}
```

```bash
openbb --spec eodhd.spec --describe eod
```

```json
{
  "parameters": [
    {"name": "ticker", "in": "path", "type": "string", "required": true, "help": "Ticker symbol of the company"},
    {"name": "fmt", "in": "query", "type": "string", "default": "json", "choices": ["json", "csv"]},
    {"name": "from", "in": "query", "type": "string", "help": "Start date (YYYY-MM-DD)"},
    {"name": "to", "in": "query", "type": "string", "help": "End date (YYYY-MM-DD)"},
    {"name": "period", "in": "query", "type": "string", "choices": ["d", "w", "m"]},
    {"name": "api_token", "in": "query", "type": "string", "help": "EODHD API key"}
  ]
}
```

### Auth

EODHD takes the key as a query param, not a header — use `-Q`:

```bash
openbb --spec eodhd.spec eod --ticker AAPL.US
# {"ok": false, "error": {"type": "HTTP401", "message": "Unauthenticated"}}

openbb --spec eodhd.spec -Q api_token=demo eod --ticker AAPL.US --from 2024-01-01 --to 2024-01-10
```

`demo` is EODHD's public demo key — real, rate-limited, and scoped to `AAPL.US`/`VTI.US`/a handful
of other symbols, which is why every example below uses it. Swap in
`OPENBB_HTTP_QUERY_API_TOKEN=<your key>` (or `-Q api_token=...`) for unrestricted symbols.

**Gotcha found while testing:** the spec declares `fmt` defaults to `json`, but EODHD's live API
actually defaults to CSV when `fmt` is omitted — pass `--fmt json` explicitly if you want structured
output. Published API docs and live behavior drift; this is exactly the kind of thing worth
smoke-testing before building automation on a generated spec.

### Pull real data

```bash
openbb --spec eodhd.spec -Q api_token=demo eod \
       --ticker AAPL.US --fmt json --from 2024-01-01 --to 2024-01-10
```

```json
{"ok": true, "result": [
  {"date": "2024-01-02", "open": 187.15, "high": 188.44, "low": 183.89, "close": 185.64, "adjusted_close": 183.404, "volume": 82488700},
  {"date": "2024-01-03", "open": 184.22, "high": 185.88, "low": 183.43, "close": 184.25, "adjusted_close": 182.0308, "volume": 58414500},
  {"date": "2024-01-10", "open": 184.35, "high": 186.4, "low": 183.92, "close": 186.19, "adjusted_close": 183.9474, "volume": 46792900}
]}
```

```bash
openbb --spec eodhd.spec -Q api_token=demo real-time --ticker AAPL.US
# {"ok": true, "result": {"code": "AAPL.US", "close": 335.92, "previousClose": 337.02, "change": -1.1, "change_p": -0.3264, ...}}

openbb --spec eodhd.spec -Q api_token=demo fundamentals --ticker AAPL.US
# {"ok": true, "result": {"General": {"Code": "AAPL", "Name": "Apple Inc.", "Sector": "Technology", "Industry": "Consumer Electronics", ...}}}
```

### Batch mode — several EODHD calls in one shot

```bash
cat <<'EOF' | openbb --spec eodhd.spec -Q api_token=demo --batch
{"id":"eod","command":"eod","params":{"ticker":"AAPL.US","fmt":"json","from":"2024-06-01","to":"2024-06-05"}}
{"id":"rt","command":"real-time","params":{"ticker":"AAPL.US"}}
EOF
```

```json
{"id": "eod", "ok": true, "result": [
  {"date": "2024-06-03", "open": 192.9, "high": 194.99, "low": 192.52, "close": 194.03, "volume": 50080500},
  {"date": "2024-06-04", "open": 194.64, "high": 195.32, "low": 193.03, "close": 194.35, "volume": 47471400},
  {"date": "2024-06-05", "open": 195.4, "high": 196.9, "low": 194.87, "close": 195.87, "volume": 54156800}
]}
{"id": "rt", "ok": true, "result": {"code": "AAPL.US", "close": 335.92, "change_p": -0.3264}}
```

### From JSON to a DataFrame

The CLI's job stops at a JSON envelope; pandas takes it from there:

```python
import json
import subprocess
import pandas as pd

out = subprocess.run(
    [
        "openbb",
        "--spec",
        "eodhd.spec",
        "-Q",
        "api_token=demo",
        "eod",
        "--ticker",
        "AAPL.US",
        "--fmt",
        "json",
        "--from",
        "2024-01-01",
        "--to",
        "2024-03-29",
    ],
    capture_output=True,
    text=True,
    check=True,
).stdout

rows = json.loads(out)["result"]
df = pd.DataFrame(rows)
df["date"] = pd.to_datetime(df["date"])
df = df.set_index("date")

print(df[["close", "volume"]].describe())
print(df["close"].rolling(20).mean().tail())
```

```
            close        volume
count   61.000000  6.100000e+01
mean   181.830328  6.200321e+07
std      7.413215  1.924904e+07
min    169.000000  4.047780e+07
25%    173.720000  4.912840e+07
50%    182.680000  5.652950e+07
75%    186.860000  6.966880e+07
max    195.180000  1.366826e+08

date
2024-03-22    174.7270
2024-03-25    174.2115
2024-03-26    173.5655
2024-03-27    173.1600
2024-03-28    172.6965
Name: close, dtype: float64
```

Once `openbb-eodhd` is installed and built (Part 1), the same exploration reads more naturally off
the typed surface: `obb.eodhd.eod(ticker="AAPL.US", from_="2024-01-01", to="2024-03-29", api_token="demo").to_df()`.

## Reference

- `cli/README.md` — full flag reference for `--generate-spec`, `--generate-extension`, multi-spec
  mounting, auth hooks, batch mode, and the REPL.
- NY Fed Markets API: <https://markets.newyorkfed.org/static/docs/markets-api.yml>
- EODHD OpenAPI document: <https://eodhistoricaldata.github.io/EODHD-openapi/openapi.yaml>