Skip to content

Factor DSL

shadow-factor uses a formula-oriented DSL to define reusable factors.

The goal is to describe factor logic declaratively instead of scattering business meaning across custom scripts.

Why A DSL

A factor DSL makes it easier to:

  • express factor logic as readable formulas
  • reuse the same definition in research and production
  • register factors as named assets
  • compare strategies that use different factor formulas
  • version factor definitions more safely

Common Building Blocks

Base fields

"net_profit"
"operating_revenue"
"total_assets"

MRQ-style references

"net_profit_mrq_0"
"net_profit_mrq_1"

Financial operators

"TTM(net_profit)"
"YoY(net_profit)"
"QoQ(net_profit)"

Arithmetic composition

"TTM(net_profit) / TTM(operating_revenue)"
"TTM(net_profit) / total_assets"
"(TTM(net_profit) - TTM(tax)) / total_assets"

Safer ratio construction

"SafeDiv(TTM(net_profit), total_assets)"

Example Patterns

1. Turn one field into a usable factor

"TTM(net_profit)"

2. Turn a business intuition into a ratio

"SafeDiv(TTM(net_profit), total_equity)"

3. Turn a level factor into a growth factor

"YoY(TTM(net_profit))"

4. Turn multiple statements into one combined signal

"SafeDiv(TTM(operating_cash_flow), TTM(net_profit))"

Ad Hoc vs Registered

The DSL supports two important workflows.

Ad hoc evaluation

Good for research, testing, and fast iteration:

import shadow_factor as sf

trial_df = sf.panel(
    "SafeDiv(TTM(net_profit), total_assets)",
    start_date="2024-01-01",
)

Registered factor

Good for repeated use and shared definitions:

import shadow_factor as sf

sf.register_factor(
    name="roa_ttm",
    formula="SafeDiv(TTM(net_profit), total_assets)",
    description="ROA based on trailing-twelve-month net profit",
)

Design Principle

A good factor formula should be:

  • readable by a researcher
  • stable enough to reuse
  • close to the business meaning it represents
  • easy to connect to strategy specs in shadow