Skip to content

Add balances_bitcoin_daily model#9368

Closed
0xBoxer wants to merge 1 commit intomainfrom
feat/bitcoin-daily-balances
Closed

Add balances_bitcoin_daily model#9368
0xBoxer wants to merge 1 commit intomainfrom
feat/bitcoin-daily-balances

Conversation

@0xBoxer
Copy link
Copy Markdown
Collaborator

@0xBoxer 0xBoxer commented Feb 25, 2026

Summary

  • Adds a new balances_bitcoin_daily incremental model that tracks daily BTC balances per wallet address
  • Follows the established forward-fill pattern from stablecoins_tron_balances_from_transfers — the same architecture used across all stablecoin balance models
  • Replaces the broken approach in balances_bitcoin_satoshi_day which had a missing satoshi→BTC conversion, bogus "profit"/"total_asset" columns, and unmaterialized views that couldn't execute at scale

How it works

  1. UTXO flow extractionbitcoin.outputs as deposits (credit), bitcoin.inputs as withdrawals (debit)
  2. Daily aggregation — Net daily inflow/outflow per wallet
  3. Incremental prior balances — On incremental runs, fetches last known balance from {{ this }} to avoid recomputing from genesis
  4. Forward-fill gap join — Carries the last known balance forward across inactive days using lead(day) + date spine
  5. USD pricing — Proper satoshi / 1e8 * BTC price at the final layer

Output columns

Column Description
blockchain Always 'bitcoin'
day Date
wallet_address Bitcoin address
balance_satoshi Cumulative balance in satoshis (forward-filled)
balance_btc Balance in BTC (balance_satoshi / 1e8)
btc_price_usd BTC price on this day
balance_usd USD value of holdings
last_updated Block time of most recent transaction affecting this balance

Config

  • materialized = 'incremental' with merge strategy
  • partition_by = ['day']
  • unique_key = ['day', 'wallet_address']
  • incremental_predicates for partition pruning
  • Schema tests: unique_combination_of_columns + not_null on balance_satoshi

Test plan

  • Model compiles cleanly (dbt compile --select balances_bitcoin_daily ✅ verified locally)
  • CI runs the model against Dune API
  • Verify output data quality: spot-check known wallet balances
  • Verify incremental runs produce consistent results with full refresh

Made with Cursor

New incremental balance model for Bitcoin using the established
forward-fill pattern from stablecoins_tron_balances_from_transfers.

- Tracks UTXO-based balances: outputs as deposits, inputs as withdrawals
- Proper satoshi-to-BTC conversion (/ 1e8)
- Forward-fills balances across inactive days
- Incremental merge with prior_balances from {{ this }}
- Partitioned by day with incremental_predicates
- Includes unique_combination_of_columns + not_null tests

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented Feb 25, 2026

PR Summary

Medium Risk
Adds a new incremental, forward-filled balance table over long date ranges and per-address partitions, which can be costly or incorrect if incremental predicates/date spine joins behave unexpectedly; otherwise it’s isolated to a new model and schema entry.

Overview
Adds a new dbt model, balances_bitcoin_daily, that produces daily per-wallet cumulative BTC balances from bitcoin.outputs (deposits) and bitcoin.inputs (withdrawals), forward-filling balances across inactive days.

The model is incremental/merge partitioned by day with a prior_balances bootstrap for incremental runs, and enriches output with USD valuation via prices.usd plus schema tests (unique_combination_of_columns on day+wallet_address and not_null on balance_satoshi).

Written by Cursor Bugbot for commit 150cc7f. Configure here.

@github-actions github-actions Bot marked this pull request as draft February 25, 2026 11:54
@github-actions github-actions Bot added WIP work in progress dbt: tokens covers the Tokens dbt subproject labels Feb 25, 2026
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

left join {{ source('prices', 'usd') }} as p
on f.day = p.minute
and p.symbol = 'BTC'
and p.blockchain is null
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Price join may produce duplicate rows

High Severity

The join f.day = p.minute compares a date column with a timestamp column. In Spark SQL this typically casts the timestamp to date, so all price rows whose date matches f.day are returned. If prices.usd has multiple rows per day for BTC (e.g. minute or hourly granularity), the join produces multiple rows per (day, wallet_address), violating the model unique_key and causing merge failures or incorrect data.

Fix in Cursor Fix in Web

and p.symbol = 'BTC'
and p.blockchain is null
where f.balance_satoshi > 0
and f.wallet_address is not null
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zero-balance rows excluded, diverges from reference pattern

Medium Severity

The model filters f.balance_satoshi > 0, which drops the day a wallet empties. The reference pattern in stablecoins_tron_balances_from_transfers and stablecoins_balances_from_transfers keeps zero-balance rows when cast(f.last_updated as date) = f.day so the day of the actual balance change is recorded.

Fix in Cursor Fix in Web

{% if is_incremental() %}
and {{ incremental_predicate('i.block_time') }}
{% endif %}
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null checks on block_date and block_time

Medium Severity

The deposits and withdrawals CTEs do not filter out rows where block_date or block_time is null. Null block_date becomes null day, which flows into daily_aggregated and changed_balances. The forward-fill join d.day >= b.day fails to match when b.day is null, so those transactions are dropped. Transactions with null block_time can produce null last_updated and affect ordering in window functions.

Fix in Cursor Fix in Web

@jeff-dude jeff-dude closed this Apr 1, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dbt: tokens covers the Tokens dbt subproject WIP work in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants