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