Metadata-Version: 2.4
Name: trail-fmp
Version: 0.3.0
Summary: Financial Modeling Prep data source for Trail: dual-frequency statements, ratios, and prices via aiofmp
Project-URL: Homepage, https://github.com/trail-language
Project-URL: Repository, https://github.com/trail-language/trail-fmp
Project-URL: Issues, https://github.com/trail-language/trail-fmp/issues
Author-email: Usman Shahid <u.manshahid@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: finance,financial-modeling-prep,financial-statements,fmp,polars,ratios,trail
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.11
Requires-Dist: aiofmp>=1.3
Requires-Dist: polars>=1.20
Requires-Dist: trail-lang>=0.14
Description-Content-Type: text/markdown

# trail-fmp

Financial Modeling Prep data source for [Trail](https://github.com/trail-language), via the
[`aiofmp`](https://pypi.org/project/aiofmp/) async client.

FMP returns already-normalized statement line items, so `trail-fmp` maps them directly onto
Trail's canonical schema and serves them at **both annual and quarterly** frequency - use the
frequency prefix to pick, or reference bare fields for the annual default:

```trail
model growth at quarterly {
  export rev_ttm = ttm(quarterly.income.revenue)
  export roe     = fmp.roe
  export pe      = daily.price.adj_close / annual.income.eps_diluted
}
```

## Provides

- **Statements** (annual + quarterly, 39 fields):
  - `income.*`: revenue, cogs, gross_profit, operating_income, net_income, interest_expense,
    income_tax_expense, income_before_tax, eps_diluted, weighted_average_shares_diluted,
    weighted_average_shares, ebitda, depreciation_amortization, sga
  - `balance.*`: total_assets, current_assets, current_liabilities, total_liabilities,
    long_term_debt, total_debt, total_equity, retained_earnings, accounts_receivable, inventory,
    accounts_payable, net_fixed_assets, cash_and_equivalents, cash_and_short_term_investments,
    minority_interest, common_stock, goodwill
  - `cash.*`: cfo, capex, free_cash_flow, stock_issued, cfi, cff, net_change_in_cash,
    dividends_paid (`capex` and `dividends_paid` are normalized to positive magnitudes)
- **Meta**: `meta.sector`, `meta.exchange`, `meta.is_active`, `meta.country` are a **current
  snapshot** broadcast onto every period; `meta.country` is normalized to ISO3, so it bridges to
  country-keyed sources like `trail-gmd`. `meta.market_cap` is real per-period data from
  key-metrics (naive placement - not filing-lag-adjusted).
- **Price**: `price.adj_close` - dividend-adjusted close, sampled at each period-end for
  statement frequencies, or one row per trading day at `daily`.
- **`fmp.*`** ratio / metric / score vocabulary (49 fields):
  - margins & profitability: `gross_margin`, `net_margin`, `ebit_margin`, `ebitda_margin`,
    `operating_margin`, `pretax_margin`, `effective_tax_rate`
  - liquidity & leverage: `current_ratio`, `quick_ratio`, `cash_ratio`, `debt_to_equity`,
    `debt_to_assets`, `financial_leverage`, `interest_coverage`
  - turnover: `asset_turnover`, `inventory_turnover`, `receivables_turnover`, `payables_turnover`
  - price multiples & dividends: `pe`, `pb`, `ps`, `price_to_fcf`, `peg`, `dividend_yield`,
    `dividend_per_share`, `payout_ratio`
  - returns & valuation: `roe`, `roa`, `roic`, `roce`, `earnings_yield`, `ev_to_sales`,
    `ev_to_ebitda`, `ev_to_fcf`, `fcf_yield`, `enterprise_value`, `graham_number`,
    `graham_net_net`, `invested_capital`, `working_capital`, `income_quality`,
    `capex_to_revenue`, `capex_to_depreciation`
  - operating cycle: `dso`, `dio`, `dpo`, `ccc`
  - quality scores (current snapshot): `piotroski`, `altman_z`
- **Entity dimension**: `entity` (tickers) - a plain entity-keyed source.
- **Frequencies**: `annual`, `quarterly` for statements/ratios/meta; `daily` for `price.adj_close`
  only (statements/ratios/meta have no daily axis).
- **Unavailable**: `price.dividends` - aiofmp has no historical per-share dividends endpoint; use
  `fmp.dividend_yield` / `fmp.dividend_per_share` instead.

## Configure

```yaml
# trail.yaml
sources:
  fmp:
    driver: fmp
    options:
      api_key: ${FMP_API_KEY}     # or set the FMP_API_KEY environment variable
      tickers: [AAPL, MSFT]        # or let a model's universe scope the fetch
      cached: true                 # optional: aiofmp's Parquet cache
      limit: 20                    # periods fetched per statement
precedence:
  default: [fmp]
```

## Options

| Option | Type | Default | Meaning |
| --- | --- | --- | --- |
| `api_key` | str | *(required)* | FMP API key; falls back to the `FMP_API_KEY` env var. Missing both raises `E-FMP-KEY`. |
| `tickers` | list[str] | `[]` | The fetch universe (uppercased). A model's own universe (via `request.entities`) scopes a given load regardless of this setting. |
| `universe` | str | `None` | Named-universe fallback. Accepted for parity with other sources but not currently consulted by this source's `entities()` - only `tickers` scopes the fetch today. |
| `cached` | bool | `false` | Wrap the client in aiofmp's `CachedClient` (Parquet-backed) to avoid refetching across runs. |
| `cache_dir` | str | aiofmp's default | Parquet cache location, passed to `ParquetStorage`. Only read when `cached: true`. |
| `limit` | int | `20` | Periods fetched per statement endpoint. Auto-expanded (with margin) to cover a requested period window, so it's never silently truncated. |
| `requests_per_minute` | int | `None` (aiofmp's default) | Caps the request rate against FMP, to avoid 429s when many tickers are fetched concurrently. |
| `pit` | str | `None` | Set to `naive` to disable point-in-time placement for this source - every field falls back to period-end. |

## Point-in-time

Statement fields (`income.*`, `balance.*`, `cash.*`) and the `fmp.*` ratio/metric fields computed
from them (the `ratios` and `metrics` endpoints) carry a `__date:filing_date` coordinate - taken
from the statement's `acceptedDate`, falling back to `filingDate` - so the PIT engine places each
value by when it was actually knowable (lookahead-safe) rather than at period-end.

Snapshot fields (`meta.*` profile fields and the `fmp.*` quality scores) and `price.adj_close`
stay naive (period-end). Set `options.pit: naive` to disable PIT placement for this source
entirely.

Querying `fmp.*` ratio/metric fields **without** any statement field alongside them loses the
filing-date coordinate (only the statement loop attaches it) and falls back to naive placement -
include a statement field in the same query to get lookahead-safe placement for those `fmp.*`
fields too.

## Notes

- `meta.*` (profile) and the `fmp.*` quality scores are current snapshots broadcast onto each
  period - not point-in-time series.
- Some balance/cash-flow wire names follow the standard FMP `/stable` spelling; if a field
  returns null across the board, verify it against a live pull (`pytest -m live`).
- A ticker that fails to load (paywalled endpoint, delisted symbol, transient error) is skipped
  with a warning rather than aborting the whole panel, unless every ticker fails or the error is
  auth/config-class.
