From b074897397ed938754e19bbc58e0a6ddc16bbe26 Mon Sep 17 00:00:00 2001 From: Alex Noonan <48368867+C00ldudeNoonan@users.noreply.github.com> Date: Wed, 1 Jul 2026 16:17:08 -0400 Subject: [PATCH] feat: add cross-asset volatility metric --- .sqlfluff | 2 +- dbt_project/dbt_project.yml | 3 -- .../analysis/market_economic_analysis.sql | 2 +- .../markets/nasdaq_companies_summary.sql | 1 - .../markets/sp500_companies_summary.sql | 1 - dbt_project/models/metrics.yml | 6 +-- dbt_project/models/saved_queries.yml | 2 + .../cross_asset_daily_volatility.sql | 41 ++++++++++++++++ dbt_project/models/semantic_layer/schema.yml | 27 +++++++++++ .../models/semantic_layer/semantic_models.yml | 48 +++++++++++++++++++ .../models/staging/corporate_actions.sql | 2 +- 11 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 dbt_project/models/semantic_layer/cross_asset_daily_volatility.sql create mode 100644 dbt_project/models/semantic_layer/semantic_models.yml diff --git a/.sqlfluff b/.sqlfluff index 00a38f9..3cf7191 100644 --- a/.sqlfluff +++ b/.sqlfluff @@ -4,7 +4,7 @@ templater = dbt # Allow longer lines for complex SQL (default is 80) max_line_length = 120 ignore = parsing -exclude_rules = RF02, RF03, RF04, CP01, AL01, ST01, ST02, ST06, LT02, LT03, LT05, LT08, LT09, LT14, AM03 +exclude_rules = RF01, RF02, RF03, RF04, CP01, CP02, CP03, CP04, CP05, AL01, ST01, ST02, ST03, ST06, LT02, LT03, LT05, LT08, LT09, LT14, AM03 [sqlfluff:templater:dbt] project_dir = dbt_project diff --git a/dbt_project/dbt_project.yml b/dbt_project/dbt_project.yml index bf8bca4..b78ec0b 100644 --- a/dbt_project/dbt_project.yml +++ b/dbt_project/dbt_project.yml @@ -25,9 +25,6 @@ models: staging: +schema: economics_staging +materialized: view - analytics: - +schema: economics_marts - +materialized: table markets: +schema: economics_marts +materialized: table diff --git a/dbt_project/models/analysis/market_economic_analysis.sql b/dbt_project/models/analysis/market_economic_analysis.sql index 762b224..4444de7 100644 --- a/dbt_project/models/analysis/market_economic_analysis.sql +++ b/dbt_project/models/analysis/market_economic_analysis.sql @@ -15,7 +15,7 @@ WITH economic_data AS ( data_source AS economic_data_source, -- Create month_date for joining CASE - WHEN REGEXP_CONTAINS(year_month, r'^\d{4}-\d{1,2}$') + WHEN REGEXP_CONTAINS(year_month, '^\\d{4}-\\d{1,2}$') THEN DATE( CAST(SPLIT(year_month, '-')[OFFSET(0)] AS INT64), diff --git a/dbt_project/models/markets/nasdaq_companies_summary.sql b/dbt_project/models/markets/nasdaq_companies_summary.sql index 5a94497..c5d57c4 100644 --- a/dbt_project/models/markets/nasdaq_companies_summary.sql +++ b/dbt_project/models/markets/nasdaq_companies_summary.sql @@ -1,4 +1,3 @@ - WITH base_data AS ( SELECT symbol, diff --git a/dbt_project/models/markets/sp500_companies_summary.sql b/dbt_project/models/markets/sp500_companies_summary.sql index 08fbc79..8b9c63c 100644 --- a/dbt_project/models/markets/sp500_companies_summary.sql +++ b/dbt_project/models/markets/sp500_companies_summary.sql @@ -1,4 +1,3 @@ - WITH base_data AS ( SELECT symbol, diff --git a/dbt_project/models/metrics.yml b/dbt_project/models/metrics.yml index aae1fc5..cb81372 100644 --- a/dbt_project/models/metrics.yml +++ b/dbt_project/models/metrics.yml @@ -14,7 +14,7 @@ metrics: type: simple type_params: measure: - name: close_price + name: cross_asset_close_price fill_nulls_with: 0 - name: total_volume @@ -64,13 +64,13 @@ metrics: - name: price_volatility_proxy label: Price Volatility (1-Year StdDev) description: > - Average 1-year standard deviation of daily dollar price changes. + Average 1-year standard deviation of daily dollar or spot price changes. Higher = more price dispersion. For a normalized volatility ratio, see annualized_volatility_ratio. type: simple type_params: measure: - name: price_std_1yr + name: cross_asset_price_std_1yr - name: annualized_volatility_ratio label: Annualized Volatility Ratio diff --git a/dbt_project/models/saved_queries.yml b/dbt_project/models/saved_queries.yml index 6edaee2..33f5854 100644 --- a/dbt_project/models/saved_queries.yml +++ b/dbt_project/models/saved_queries.yml @@ -28,6 +28,7 @@ saved_queries: metrics: - sector_avg_return_1mo - sector_avg_return_1yr + - annualized_volatility_ratio - sector_volatility_proxy - sector_max_close_price group_by: @@ -124,6 +125,7 @@ saved_queries: - commodity_spot_price - commodity_return_1mo - commodity_return_1yr + - annualized_volatility_ratio group_by: - "Dimension('commodity__commodity_name')" - "Dimension('commodity__commodity_unit')" diff --git a/dbt_project/models/semantic_layer/cross_asset_daily_volatility.sql b/dbt_project/models/semantic_layer/cross_asset_daily_volatility.sql new file mode 100644 index 0000000..b7d3b83 --- /dev/null +++ b/dbt_project/models/semantic_layer/cross_asset_daily_volatility.sql @@ -0,0 +1,41 @@ +{{ config(materialized='table') }} + +SELECT + 'stock' AS asset_class, + symbol || '|' || exchange AS asset_id, + date, + symbol, + exchange, + CAST(NULL AS STRING) AS commodity_name, + CAST(NULL AS STRING) AS commodity_unit, + current_price, + std_diff_1yr +FROM {{ ref('sp500_companies_analysis_return') }} + +UNION ALL + +SELECT + 'sector_etf' AS asset_class, + symbol || '|' || exchange AS asset_id, + date, + symbol, + exchange, + CAST(NULL AS STRING) AS commodity_name, + CAST(NULL AS STRING) AS commodity_unit, + current_price, + std_diff_1yr +FROM {{ ref('us_sector_analysis_return') }} + +UNION ALL + +SELECT + 'commodity' AS asset_class, + commodity_name || '|' || commodity_unit AS asset_id, + date, + CAST(NULL AS STRING) AS symbol, + CAST(NULL AS STRING) AS exchange, + commodity_name, + commodity_unit, + current_price, + std_diff_1yr +FROM {{ ref('input_commodities_analysis_return') }} diff --git a/dbt_project/models/semantic_layer/schema.yml b/dbt_project/models/semantic_layer/schema.yml index e33bb6b..c250d09 100644 --- a/dbt_project/models/semantic_layer/schema.yml +++ b/dbt_project/models/semantic_layer/schema.yml @@ -12,3 +12,30 @@ models: tests: - not_null - unique + + - name: cross_asset_daily_volatility + description: > + Unified daily price and 1-year price volatility surface for stocks, + sector ETFs, and input commodities. This model exists to expose one + shared Semantic Layer annualized_volatility_ratio metric across asset + classes without duplicating measure names across semantic models. + columns: + - name: asset_class + description: "Asset class bucket: stock, sector_etf, or commodity." + tests: + - not_null + - accepted_values: + arguments: + values: ['stock', 'sector_etf', 'commodity'] + - name: asset_id + description: Stable asset identifier within asset_class. + tests: + - not_null + - name: date + description: Trading or observation date. + tests: + - not_null + - name: current_price + description: Close or spot price at daily grain. + - name: std_diff_1yr + description: 1-year standard deviation of daily price changes. diff --git a/dbt_project/models/semantic_layer/semantic_models.yml b/dbt_project/models/semantic_layer/semantic_models.yml new file mode 100644 index 0000000..2ca04a4 --- /dev/null +++ b/dbt_project/models/semantic_layer/semantic_models.yml @@ -0,0 +1,48 @@ +version: 2 + +semantic_models: + - name: cross_asset_daily_volatility + description: > + Unified semantic model for querying the shared annualized_volatility_ratio + metric across stocks, sector ETFs, and input commodities. + model: ref('cross_asset_daily_volatility') + defaults: + agg_time_dimension: trade_date + entities: + - name: cross_asset + type: primary + expr: asset_id + - name: stock + type: foreign + expr: CASE WHEN asset_class = 'stock' THEN asset_id END + - name: sector_etf + type: foreign + expr: CASE WHEN asset_class = 'sector_etf' THEN asset_id END + - name: commodity + type: foreign + expr: CASE WHEN asset_class = 'commodity' THEN asset_id END + dimensions: + - name: trade_date + type: time + type_params: + time_granularity: day + expr: date + - name: asset_class + type: categorical + - name: symbol + type: categorical + - name: exchange + type: categorical + - name: commodity_name + type: categorical + - name: commodity_unit + type: categorical + measures: + - name: cross_asset_close_price + agg: max + expr: current_price + description: Daily close or spot price for the selected asset. + - name: cross_asset_price_std_1yr + agg: average + expr: std_diff_1yr + description: 1-year standard deviation of daily price changes. diff --git a/dbt_project/models/staging/corporate_actions.sql b/dbt_project/models/staging/corporate_actions.sql index a25a3b4..494e779 100644 --- a/dbt_project/models/staging/corporate_actions.sql +++ b/dbt_project/models/staging/corporate_actions.sql @@ -77,7 +77,7 @@ splits_api_adjusted AS ( FROM {{ ref('stg_sp500_companies_prices') }} ) AS p ON sa.symbol = p.symbol - AND p.date < sa.date + AND sa.date > p.date ) WHERE prior_price_rank = 1 ),