Skip to content

Commit 7fbeb34

Browse files
authored
Merge pull request #2610 from DerekMelchin/bug-split-equity-fundamental-data-skill
Split equity-fundamental-data SKILL into per-family SKILLs
2 parents 6f9e5c7 + 73d9797 commit 7fbeb34

22 files changed

Lines changed: 2838 additions & 2424 deletions

File tree

skill-templates/bundle-skills.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,44 @@ def _table_cell(text: str) -> str:
236236
return " ".join(html.unescape(text).split()).replace("|", "\\|")
237237

238238

239-
def format_fundamental_lookup(lookup: dict) -> str:
239+
def _norm_path(path: str) -> str:
240+
"""Language-neutral path form: strip the `f.` root, drop underscores, lowercase —
241+
so a template scope written as `financial_statements.income_statement` matches both
242+
`f.financial_statements.income_statement...` and `f.FinancialStatements.IncomeStatement...`."""
243+
p = path[2:] if path[:2] in ("f.", "F.") else path
244+
return p.replace("_", "").lower()
245+
246+
247+
def format_fundamental_lookup(lookup: dict, scopes: list[str] | None = None) -> str:
240248
"""Render the data-point table (path + description), then each enum's
241249
constants — as a Value/Description table where the inspector documents them,
242-
or a plain list where it doesn't (e.g. MorningstarIndustryGroupCode)."""
250+
or a plain list where it doesn't (e.g. MorningstarIndustryGroupCode).
251+
252+
`scopes` filters rows by path prefix (comma tokens from the template marker):
253+
plain tokens include matching subtrees; `!token` excludes them (no plain
254+
tokens = start from everything). The Classification-code-constants section is
255+
emitted only when the filtered rows still contain `asset_classification` paths,
256+
since only those compare against the constants. No scopes = the full table
257+
(backward compatible)."""
258+
rows = lookup["rows"]
259+
if scopes:
260+
pos = [_norm_path(s) for s in scopes if not s.startswith("!")]
261+
neg = [_norm_path(s[1:]) for s in scopes if s.startswith("!")]
262+
263+
def keep(path: str) -> bool:
264+
p = _norm_path(path)
265+
ok = any(p.startswith(x) for x in pos) if pos else True
266+
return ok and not any(p.startswith(x) for x in neg)
267+
268+
rows = [(p, d) for p, d in rows if keep(p)]
269+
if not rows:
270+
raise SystemExit(f"fundamental-lookup scopes matched no rows: {scopes}")
271+
include_enums = any(_norm_path(p).startswith("assetclassification") for p, _ in rows)
243272
table = ["| Data point | Description |", "|---|---|"]
244-
table += [f"| `{path}` | {_table_cell(desc)} |" for path, desc in lookup["rows"]]
273+
table += [f"| `{path}` | {_table_cell(desc)} |" for path, desc in rows]
274+
275+
if not include_enums:
276+
return "\n".join(table)
245277

246278
sections = []
247279
for type_name, values in lookup["enums"].items():
@@ -370,21 +402,28 @@ def main() -> int:
370402
# Wipe the build dir so deletions in the source propagate.
371403
remove_tree(skills_root, dry_run=args.dry_run)
372404

373-
fund_lookup_marker = "<!-- fundamental-lookup -->"
374-
# Lazily fetched per language; populated only when a skill needs the lookup.
375-
fundamental_lookup: dict[str, str] = {}
405+
# `<!-- fundamental-lookup -->` = full table; `<!-- fundamental-lookup: a, b, !c -->`
406+
# = rows scoped by path prefix (see format_fundamental_lookup). The raw inspector
407+
# tree is fetched lazily, once per language, and filtered per marker.
408+
fund_marker_re = re.compile(r"<!--\s*fundamental-lookup(?::([^>]*?))?\s*-->")
409+
fundamental_lookup_raw: dict[str, dict] = {}
376410

377411
# Build: split each skill into a Python and a C# tree.
378412
for skill in skills:
379413
for lang in LANGS:
380414
content = split_for_language(skill.content, lang)
381-
if fund_lookup_marker in content:
382-
if lang not in fundamental_lookup:
415+
if fund_marker_re.search(content):
416+
if lang not in fundamental_lookup_raw:
383417
print(f"Fetching Fundamental property tree ({lang}) from inspector...")
384-
fundamental_lookup[lang] = format_fundamental_lookup(
385-
fetch_fundamental_lookup(lang)
418+
fundamental_lookup_raw[lang] = fetch_fundamental_lookup(lang)
419+
420+
def _expand(m: "re.Match[str]") -> str:
421+
scopes = [t.strip() for t in (m.group(1) or "").split(",") if t.strip()]
422+
return format_fundamental_lookup(
423+
fundamental_lookup_raw[lang], scopes or None
386424
)
387-
content = content.replace(fund_lookup_marker, fundamental_lookup[lang])
425+
426+
content = fund_marker_re.sub(_expand, content)
388427
write_file(
389428
skills_root / lang / skill.rel_dir / "SKILL.md",
390429
content,
Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
---
22
name: equity-fundamental-data
3-
description: Use to look up the exact path or spelling of any Morningstar fundamental data point on a QuantConnect/LEAN `Fundamental` object `f` — every field under py`f.financial_statements.*`cs`f.FinancialStatements.*` (income statement, balance sheet, cash flow statement), the operation / valuation / earning ratios, earning reports, company profile, company & security reference, and asset classification — plus the Morningstar sector and industry classification code constants. Triggers — a missing-attribute / compile error on a Fundamental property path; questions like "what's the path to net income / operating cash flow / shares outstanding / PE ratio / sector code". Skip when — you need how to build or screen a universe (see the fundamental-universes skill).
3+
description: START HERE to look up the exact path or spelling of any Morningstar fundamental data point on a QuantConnect/LEAN `Fundamental` object `f`. This skill holds the path-reading rules, the top-level and filing-metadata fields (market cap, `period_ending_date`, `file_date`, ...), and the index of the six field-family skills that hold the full tables — fundamental-income-statement, fundamental-balance-sheet, fundamental-cash-flow-statement, fundamental-ratios, fundamental-company-data, fundamental-classification. Triggers — a missing-attribute / compile error on a Fundamental property path; questions like "what's the path to net income / operating cash flow / shares outstanding / PE ratio / sector code". Skip when — you need how to build or screen a universe (see the fundamental-universes skill).
44
---
55

66
# Fundamental data-point attributes — QuantConnect / LEAN
77

8-
Every readable Morningstar data point on a `Fundamental` object, written as a full path from the snapshot `f` with its description. Copy the path you need rather than guessing from English names — a wrong path wastes a backtest run. Get `f` from an py`add_universe(...)`cs`AddUniverse(...)` selection callback (each element is a `Fundamental`), from py`self.securities["SPY"].fundamentals`cs`Securities["SPY"].Fundamentals`, or from a history request.
8+
Morningstar data points are read as a full path from the snapshot `f` — copy the path you need rather than guessing from English names; a wrong path wastes a backtest run. Get `f` from an py`add_universe(...)`cs`AddUniverse(...)` selection callback (each element is a `Fundamental`), from py`self.securities["SPY"].fundamentals`cs`Securities["SPY"].Fundamentals`, or from a history request. The field tables are split across skills by family: THIS skill carries the top-level and filing-metadata fields plus the index below — load the family skill that holds your field's table.
9+
10+
## Where every field lives — load the matching skill
11+
12+
| Field family | Load this skill | Contents |
13+
|---|---|---|
14+
| py`f.financial_statements.income_statement.*`cs`f.FinancialStatements.IncomeStatement.*` | `fundamental-income-statement` | revenue, cost/expense lines, operating & net income, EBIT/EBITDA, interest, tax, dividends paid |
15+
| py`f.financial_statements.balance_sheet.*`cs`f.FinancialStatements.BalanceSheet.*` | `fundamental-balance-sheet` | assets, liabilities, equity, debt, working-capital components, share counts |
16+
| py`f.financial_statements.cash_flow_statement.*`cs`f.FinancialStatements.CashFlowStatement.*` | `fundamental-cash-flow-statement` | operating / investing / financing cash flows, capex, issuance & repurchase, dividends |
17+
| py`f.operation_ratios.*`cs`f.OperationRatios.*`, py`f.valuation_ratios.*`cs`f.ValuationRatios.*`, py`f.earning_ratios.*`cs`f.EarningRatios.*` | `fundamental-ratios` | ROA/ROE/margins/turnover, PE/PB/PS/EV multiples & yields, EPS/DPS growth rates |
18+
| py`f.earning_reports.*`cs`f.EarningReports.*`, py`f.company_reference.*`cs`f.CompanyReference.*`, py`f.security_reference.*`cs`f.SecurityReference.*`, py`f.company_profile.*`cs`f.CompanyProfile.*` | `fundamental-company-data` | EPS & report dates, listing/exchange/share-class reference, company profile basics |
19+
| py`f.asset_classification.*`cs`f.AssetClassification.*` + code constants | `fundamental-classification` | sector / industry-group / industry codes and the `MorningstarSectorCode`-style constants they compare against |
920

1021
## Reading the paths
1122

1223
- A path ending in `.[value 1M 2M 3M 6M 9M 12M]` is a `MultiPeriodField` — append **one** period accessor to read the number. py`.value`cs`.Value` is the most recent reported period; the `1M``12M` tokens are py`.one_month .two_months .three_months .six_months .nine_months .twelve_months`cs`.OneMonth .TwoMonths .ThreeMonths .SixMonths .NineMonths .TwelveMonths` respectively (trailing-twelve-month at `12M`). e.g. py`f.financial_statements.income_statement.net_income.twelve_months`cs`f.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths`. Forgetting the accessor is silent — the wrapper compares as truthy and numeric inequalities give nonsense.
1324
- A path with **no** bracket is read directly. e.g. py`f.valuation_ratios.pe_ratio`cs`f.ValuationRatios.PERatio`.
14-
- The integer `*_code` fields under `asset_classification` compare against the named constants in the **Classification code constants** section at the end, e.g. py`f.asset_classification.morningstar_sector_code == MorningstarSectorCode.TECHNOLOGY`cs`f.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Technology`.
25+
- The integer `*_code` fields under `asset_classification` compare against the named constants in the **fundamental-classification** skill, e.g. py`f.asset_classification.morningstar_sector_code == MorningstarSectorCode.TECHNOLOGY`cs`f.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Technology`.
26+
27+
## Top-level and filing-metadata data points
1528

16-
## Data points
29+
The snapshot's own attributes and the filing/timing fields under py`f.financial_statements`cs`f.FinancialStatements` (period end, file date, period type, ...) — the fields every point-in-time strategy needs:
1730

18-
<!-- fundamental-lookup -->
31+
<!-- fundamental-lookup: !financial_statements.income_statement, !financial_statements.balance_sheet, !financial_statements.cash_flow_statement, !operation_ratios, !valuation_ratios, !earning_ratios, !earning_reports, !company_reference, !security_reference, !company_profile, !asset_classification -->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
name: fundamental-balance-sheet
3+
description: Use to look up the exact path or spelling of any Morningstar BALANCE SHEET field on a QuantConnect/LEAN `Fundamental` object — everything under py`f.financial_statements.balance_sheet.*`cs`f.FinancialStatements.BalanceSheet.*` — assets, liabilities, stockholders' equity, debt, working-capital components, and share counts. Triggers — "path to total assets / total equity / long-term debt / cash and equivalents / inventory / shares issued". For other field families start at the equity-fundamental-data skill.
4+
---
5+
6+
# Balance-sheet fields — `Fundamental` data points
7+
8+
Full path from the snapshot `f` with the field's description — copy the path rather than guessing from English names; a wrong path wastes a backtest run. The path-reading rules and the index of all field-family skills are in the **equity-fundamental-data** skill.
9+
10+
## Reading the paths
11+
12+
- A path ending in `.[value 1M 2M 3M 6M 9M 12M]` is a `MultiPeriodField` — append **one** period accessor to read the number. py`.value`cs`.Value` is the most recent reported period; the `1M``12M` tokens are py`.one_month .two_months .three_months .six_months .nine_months .twelve_months`cs`.OneMonth .TwoMonths .ThreeMonths .SixMonths .NineMonths .TwelveMonths` respectively (trailing-twelve-month at `12M`). e.g. py`f.financial_statements.income_statement.net_income.twelve_months`cs`f.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths`. Forgetting the accessor is silent — the wrapper compares as truthy and numeric inequalities give nonsense.
13+
- A path with **no** bracket is read directly. e.g. py`f.valuation_ratios.pe_ratio`cs`f.ValuationRatios.PERatio`.
14+
- The integer `*_code` fields under `asset_classification` compare against the named constants in the **fundamental-classification** skill.
15+
16+
## Data points
17+
18+
<!-- fundamental-lookup: financial_statements.balance_sheet -->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
name: fundamental-cash-flow-statement
3+
description: Use to look up the exact path or spelling of any Morningstar CASH FLOW STATEMENT field on a QuantConnect/LEAN `Fundamental` object — everything under py`f.financial_statements.cash_flow_statement.*`cs`f.FinancialStatements.CashFlowStatement.*` — operating / investing / financing cash flows, capital expenditure, stock issuance and repurchase, and cash dividends paid. Triggers — "path to operating cash flow / free cash flow / capex / cash dividends paid / stock repurchase". For other field families start at the equity-fundamental-data skill.
4+
---
5+
6+
# Cash-flow-statement fields — `Fundamental` data points
7+
8+
Full path from the snapshot `f` with the field's description — copy the path rather than guessing from English names; a wrong path wastes a backtest run. The path-reading rules and the index of all field-family skills are in the **equity-fundamental-data** skill.
9+
10+
## Reading the paths
11+
12+
- A path ending in `.[value 1M 2M 3M 6M 9M 12M]` is a `MultiPeriodField` — append **one** period accessor to read the number. py`.value`cs`.Value` is the most recent reported period; the `1M``12M` tokens are py`.one_month .two_months .three_months .six_months .nine_months .twelve_months`cs`.OneMonth .TwoMonths .ThreeMonths .SixMonths .NineMonths .TwelveMonths` respectively (trailing-twelve-month at `12M`). e.g. py`f.financial_statements.income_statement.net_income.twelve_months`cs`f.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths`. Forgetting the accessor is silent — the wrapper compares as truthy and numeric inequalities give nonsense.
13+
- A path with **no** bracket is read directly. e.g. py`f.valuation_ratios.pe_ratio`cs`f.ValuationRatios.PERatio`.
14+
- The integer `*_code` fields under `asset_classification` compare against the named constants in the **fundamental-classification** skill.
15+
16+
## Data points
17+
18+
<!-- fundamental-lookup: financial_statements.cash_flow_statement -->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
name: fundamental-classification
3+
description: Use to look up the exact path or spelling of any Morningstar ASSET CLASSIFICATION field on a QuantConnect/LEAN `Fundamental` object — everything under py`f.asset_classification.*`cs`f.AssetClassification.*` (Morningstar sector / industry-group / industry codes, style box, financial health grade) — plus the named code constants (`MorningstarSectorCode`, `MorningstarIndustryGroupCode`, `MorningstarIndustryCode`, ...) the integer `*_code` fields compare against. Triggers — "path to sector code / industry group code / how do I filter to Technology / SIC or NAICS code". For other field families start at the equity-fundamental-data skill.
4+
---
5+
6+
# Asset-classification fields and code constants — `Fundamental` data points
7+
8+
Full path from the snapshot `f` with the field's description — copy the path rather than guessing from English names; a wrong path wastes a backtest run. The path-reading rules and the index of all field-family skills are in the **equity-fundamental-data** skill.
9+
10+
## Reading the paths
11+
12+
- A path ending in `.[value 1M 2M 3M 6M 9M 12M]` is a `MultiPeriodField` — append **one** period accessor to read the number. py`.value`cs`.Value` is the most recent reported period; the `1M``12M` tokens are py`.one_month .two_months .three_months .six_months .nine_months .twelve_months`cs`.OneMonth .TwoMonths .ThreeMonths .SixMonths .NineMonths .TwelveMonths` respectively (trailing-twelve-month at `12M`). e.g. py`f.financial_statements.income_statement.net_income.twelve_months`cs`f.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths`. Forgetting the accessor is silent — the wrapper compares as truthy and numeric inequalities give nonsense.
13+
- A path with **no** bracket is read directly. e.g. py`f.valuation_ratios.pe_ratio`cs`f.ValuationRatios.PERatio`.
14+
- The integer `*_code` fields compare against the named constants in the **Classification code constants** section at the end, e.g. py`f.asset_classification.morningstar_sector_code == MorningstarSectorCode.TECHNOLOGY`cs`f.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Technology`.
15+
16+
## Data points
17+
18+
<!-- fundamental-lookup: asset_classification -->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
name: fundamental-company-data
3+
description: Use to look up the exact path or spelling of any Morningstar EARNING REPORT, COMPANY/SECURITY REFERENCE, or COMPANY PROFILE field on a QuantConnect/LEAN `Fundamental` object — everything under py`f.earning_reports.*`cs`f.EarningReports.*` (EPS, DPS, report/file dates, shares), py`f.company_reference.*`cs`f.CompanyReference.*` (country, exchange, industry template), py`f.security_reference.*`cs`f.SecurityReference.*` (security type, primary share, listing status, IPO date), and py`f.company_profile.*`cs`f.CompanyProfile.*`. Triggers — "path to file date of the earning report / basic EPS / primary exchange / share class / is primary share / IPO date". For other field families start at the equity-fundamental-data skill.
4+
---
5+
6+
# Company data fields — `Fundamental` data points
7+
8+
Full path from the snapshot `f` with the field's description — copy the path rather than guessing from English names; a wrong path wastes a backtest run. The path-reading rules and the index of all field-family skills are in the **equity-fundamental-data** skill.
9+
10+
## Reading the paths
11+
12+
- A path ending in `.[value 1M 2M 3M 6M 9M 12M]` is a `MultiPeriodField` — append **one** period accessor to read the number. py`.value`cs`.Value` is the most recent reported period; the `1M``12M` tokens are py`.one_month .two_months .three_months .six_months .nine_months .twelve_months`cs`.OneMonth .TwoMonths .ThreeMonths .SixMonths .NineMonths .TwelveMonths` respectively (trailing-twelve-month at `12M`). e.g. py`f.financial_statements.income_statement.net_income.twelve_months`cs`f.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths`. Forgetting the accessor is silent — the wrapper compares as truthy and numeric inequalities give nonsense.
13+
- A path with **no** bracket is read directly. e.g. py`f.valuation_ratios.pe_ratio`cs`f.ValuationRatios.PERatio`.
14+
- The integer `*_code` fields under `asset_classification` compare against the named constants in the **fundamental-classification** skill.
15+
16+
## Data points
17+
18+
<!-- fundamental-lookup: earning_reports, company_reference, security_reference, company_profile -->

0 commit comments

Comments
 (0)