Skip to main content

Cryptocurrency Portfolio Tracking

Complete guide to setting up and managing cryptocurrency portfolios in Beancount.io, including multi-exchange tracking, cost basis calculations, and performance analysis.

Managing a cryptocurrency portfolio across multiple exchanges, wallets, and DeFi protocols can be challenging. This comprehensive guide shows you how to set up and maintain accurate cryptocurrency portfolio tracking using Beancount.io's powerful plain-text accounting system.

Explore a live cryptocurrency example ledger:

Open Cryptocurrency Example Ledger in a new tab

Why Traditional Portfolio Trackers Fall Short

Common Problems with Crypto Portfolio Tools

  • Limited Exchange Support: Many tools don't support all exchanges or DeFi protocols
  • Inaccurate Cost Basis: Simplified FIFO/LIFO calculations miss complex scenarios
  • Missing Transactions: API limitations and manual entry gaps
  • No Customization: Fixed categories and reporting structures
  • Privacy Concerns: Sharing API keys with third-party services
  • Vendor Lock-in: Proprietary data formats and limited export options

Beancount.io Advantages

  • Complete Control: Own your data in plain-text format
  • Unlimited Customization: Create any account structure you need
  • Precise Cost Basis: Lot-based tracking with specific identification
  • Multi-Protocol Support: Handle any exchange, wallet, or DeFi protocol
  • Transparent Calculations: See exactly how numbers are computed
  • Future-Proof: Plain-text format ensures long-term accessibility

Setting Up Your Crypto Portfolio Structure

Basic Account Hierarchy

Start with a comprehensive account structure that reflects your crypto ecosystem:

; Exchange Accounts - Organized by Platform
1970-01-01 open Assets:Crypto:Coinbase:USD
1970-01-01 open Assets:Crypto:Coinbase:BTC
1970-01-01 open Assets:Crypto:Coinbase:ETH
1970-01-01 open Assets:Crypto:Coinbase:ADA
 
1970-01-01 open Assets:Crypto:Binance:USD
1970-01-01 open Assets:Crypto:Binance:BTC
1970-01-01 open Assets:Crypto:Binance:ETH
1970-01-01 open Assets:Crypto:Binance:BNB
 
1970-01-01 open Assets:Crypto:Kraken:USD
1970-01-01 open Assets:Crypto:Kraken:BTC
1970-01-01 open Assets:Crypto:Kraken:ETH
1970-01-01 open Assets:Crypto:Kraken:ADA
1970-01-01 open Assets:Crypto:Binance:ADA
 
; Wallet Accounts - Organized by Type
1970-01-01 open Assets:Crypto:Wallet:Ledger:BTC
1970-01-01 open Assets:Crypto:Wallet:Ledger:ETH
1970-01-01 open Assets:Crypto:Wallet:MetaMask:ETH
1970-01-01 open Assets:Crypto:Wallet:MetaMask:USDC
1970-01-01 open Assets:Crypto:Wallet:MetaMask:UNI
1970-01-01 open Assets:Crypto:Wallet:TrustWallet:BNB
 
; DeFi Protocol Accounts
1970-01-01 open Assets:DeFi:Uniswap:ETH-USDC-LP
1970-01-01 open Assets:DeFi:Compound:CUSDC
1970-01-01 open Assets:Staking:Ethereum:ETH
1970-01-01 open Assets:Staking:Cardano:ADA
 
; Income Tracking
1970-01-01 open Income:Crypto:Staking:ETH
1970-01-01 open Income:Crypto:Staking:ADA
1970-01-01 open Income:Crypto:Mining:BTC
1970-01-01 open Income:Crypto:Airdrops
1970-01-01 open Income:Crypto:DeFi:Yield
1970-01-01 open Income:Crypto:Arbitrage
1970-01-01 open Income:CapitalGains:Crypto
1970-01-01 open Income:CapitalGains:LongTerm
 
; Expense Tracking
1970-01-01 open Expenses:Crypto:Fees:Trading
1970-01-01 open Expenses:Crypto:Fees:Network
1970-01-01 open Expenses:Crypto:Fees:Withdrawal
1970-01-01 open Expenses:CapitalLoss:Crypto

One naming rule bites here, and the parser enforces it rather than convention: every account component, and every commodity symbol, must start with a capital letter or a digit. Compound writes its receipt token cUSDC throughout its own interface, but Assets:DeFi:Compound:cUSDC and a cUSDC commodity are both lexer errors. Spell them CUSDC and keep the protocol's own spelling in the commodity's name: metadata.

Commodity Definitions with Metadata

Define your cryptocurrencies with rich metadata for better tracking:

1970-01-01 commodity BTC
  name: "Bitcoin"
  asset-class: "cryptocurrency"
  sector: "digital-currency"
  price-source: "coinbase"
  website: "https://bitcoin.org"
 
1970-01-01 commodity ETH
  name: "Ethereum"
  asset-class: "cryptocurrency"
  sector: "smart-contract-platform"
  price-source: "coinbase"
  website: "https://ethereum.org"
 
1970-01-01 commodity ADA
  name: "Cardano"
  asset-class: "cryptocurrency"
  sector: "smart-contract-platform"
  price-source: "binance"
  website: "https://cardano.org"
 
1970-01-01 commodity DOT
  name: "Polkadot"
  asset-class: "cryptocurrency"
  sector: "interoperability"
  price-source: "kraken"
  website: "https://polkadot.network"

Multi-Exchange Portfolio Tracking

Recording Purchases Across Exchanges

Track the same cryptocurrency across different exchanges with precise cost basis:

; Bitcoin purchases on different exchanges
2024-01-15 * "Buy BTC on Coinbase"
  Assets:Crypto:Coinbase:BTC      1.0 BTC {45000.00 USD}
  Assets:Crypto:Coinbase:USD  -45000.00 USD
  Expenses:Crypto:Fees:Trading    50.00 USD
  Assets:Crypto:Coinbase:USD     -50.00 USD
 
2024-01-20 * "Buy BTC on Binance"
  Assets:Crypto:Binance:BTC       0.5 BTC {46000.00 USD}
  Assets:Crypto:Binance:USD   -23000.00 USD
  Expenses:Crypto:Fees:Trading    25.00 USD
  Assets:Crypto:Binance:USD      -25.00 USD
 
2024-01-25 * "Buy BTC on Kraken"
  Assets:Crypto:Kraken:BTC        0.8 BTC {44000.00 USD}
  Assets:Crypto:Kraken:USD    -35200.00 USD
  Expenses:Crypto:Fees:Trading    30.00 USD
  Assets:Crypto:Kraken:USD       -30.00 USD

Cross-Exchange Transfers

Track transfers between exchanges while maintaining cost basis:

2024-02-01 * "Transfer BTC from Coinbase to Ledger"
  Assets:Crypto:Coinbase:BTC      -0.5 BTC {45000.00 USD}
  Assets:Crypto:Wallet:Ledger:BTC  0.5 BTC {45000.00 USD}
  Expenses:Crypto:Fees:Withdrawal  0.0005 BTC {45000.00 USD}
  Assets:Crypto:Coinbase:BTC      -0.0005 BTC {45000.00 USD}

Arbitrage Opportunities

An arbitrage is one economic event across two venues, so record it as one transaction. This entry sells 10 ETH you already hold on Coinbase at $2,580.00 and immediately restores the position on Binance at $2,500.00:

2024-02-10 * "Arbitrage: Buy ETH on Binance, Sell on Coinbase"
  ; Buy leg on Binance
  Assets:Crypto:Binance:ETH       10 ETH {2500.00 USD}
  Assets:Crypto:Binance:USD   -25000.00 USD
  Expenses:Crypto:Fees:Trading    25.00 USD
  Assets:Crypto:Binance:USD      -25.00 USD
  ; Sell leg on Coinbase, at the higher price
  Assets:Crypto:Coinbase:ETH     -10 ETH {2500.00 USD} @ 2580.00 USD
  Assets:Crypto:Coinbase:USD   25800.00 USD
  Expenses:Crypto:Fees:Trading    30.00 USD
  Assets:Crypto:Coinbase:USD     -30.00 USD
  Income:Crypto:Arbitrage       -800.00 USD  ; 25,800.00 proceeds - 25,000.00 basis

Three things to copy:

  • Never leave a blank line between postings. A blank line ends the transaction, and Beancount reports a syntax error on the postings after it. Use indented ; comments to label the legs instead — which is what the two comment lines above are doing.
  • The gain is the disposal's gain, $800.00, and it is negative because Income accounts hold credits. The $55.00 of fees is separate: it is what turns $800.00 of gross spread into $745.00 of profit, and it is already recorded on its own two postings.
  • The sell leg reduces a lot you must already hold. This entry assumes 10 ETH bought earlier on Coinbase at $2,500.00; without that lot the reduction has nothing to book against.

Advanced Cost Basis Management

Lot-Based Tracking

Beancount.io's lot-based system provides precise cost basis tracking:

; Multiple purchases at different prices
2024-01-01 * "BTC Purchase Lot 1"
  Assets:Crypto:Coinbase:BTC  1.0 BTC {40000.00 USD}
  Assets:Crypto:Coinbase:USD -40000.00 USD
 
2024-02-01 * "BTC Purchase Lot 2"
  Assets:Crypto:Coinbase:BTC  1.0 BTC {45000.00 USD}
  Assets:Crypto:Coinbase:USD -45000.00 USD
 
2024-03-01 * "BTC Purchase Lot 3"
  Assets:Crypto:Coinbase:BTC  1.0 BTC {50000.00 USD}
  Assets:Crypto:Coinbase:USD -50000.00 USD

The sign convention for gains and losses

Income accounts hold credits, so Beancount stores them as negative numbers. Every gain below is therefore a negative posting to an income account, and every loss is a positive posting to an expense account. The fee is separate from both: it is an expense in its own right, so subtracting it from the gain as well would deduct it twice and leave the transaction unbalanced.

For one disposal the arithmetic is always:

gain (or loss) = gross proceeds - cost basis of the lots removed
net cash       = gross proceeds - fees

Specific Identification Method

Sell specific lots for optimal tax management. Naming the lot's cost in the reduction posting is what "specific identification" means in Beancount, and it works under the default STRICT booking:

; Sell the most expensive lot: 1.0 BTC bought at $50,000.00
2024-04-01 * "Sell BTC Lot 3 for tax optimization"
  Assets:Crypto:Coinbase:BTC    -1.0 BTC {50000.00 USD} @ 48000.00 USD
  Assets:Crypto:Coinbase:USD   48000.00 USD  ; gross proceeds
  Expenses:Crypto:Fees:Trading    50.00 USD
  Assets:Crypto:Coinbase:USD     -50.00 USD
  Expenses:CapitalLoss:Crypto   2000.00 USD  ; 50,000.00 basis - 48,000.00 proceeds

Selling the oldest or the newest lot

Naming a different cost selects a different lot. These two sales take the $40,000.00 lot and then the $45,000.00 lot — the two still standing after the $50,000.00 one went in the example above — which is the same selection FIFO and LIFO would make automatically, spelled out by hand:

; Sell the oldest lot (bought 2024-01-01 at $40,000.00)
2024-05-01 * "Sell 0.5 BTC from the oldest lot"
  Assets:Crypto:Coinbase:BTC    -0.5 BTC {40000.00 USD} @ 52000.00 USD
  Assets:Crypto:Coinbase:USD   26000.00 USD  ; gross proceeds, 0.5 * 52,000.00
  Expenses:Crypto:Fees:Trading    30.00 USD
  Assets:Crypto:Coinbase:USD     -30.00 USD
  Income:CapitalGains:Crypto   -6000.00 USD  ; 26,000.00 proceeds - 20,000.00 basis
 
; Sell the newest remaining lot (bought 2024-02-01 at $45,000.00)
2024-05-02 * "Sell 0.5 BTC from the newest lot"
  Assets:Crypto:Coinbase:BTC    -0.5 BTC {45000.00 USD} @ 52000.00 USD
  Assets:Crypto:Coinbase:USD   26000.00 USD
  Expenses:Crypto:Fees:Trading    30.00 USD
  Assets:Crypto:Coinbase:USD     -30.00 USD
  Income:CapitalGains:Crypto   -3500.00 USD  ; 26,000.00 proceeds - 22,500.00 basis

Same sale price, different lot, $2,500.00 difference in realized gain. If you would rather Beancount choose the lot for you, set a booking method on the account instead of naming a cost — "FIFO", "LIFO" or "HIFO" on the open directive, described in Inventory Management.

Portfolio Performance Analysis

Price Tracking Setup

Set up automated price feeds for accurate valuation:

; Daily price updates
2024-01-15 price BTC 45000.00 USD
2024-01-15 price ETH 2500.00 USD
2024-01-15 price ADA 0.50 USD
 
2024-01-16 price BTC 46000.00 USD
2024-01-16 price ETH 2550.00 USD
2024-01-16 price ADA 0.52 USD

Portfolio Allocation Tracking

Use Beancount.io's reporting features to analyze allocation:

-- Query for portfolio allocation by asset
SELECT
  account,
  sum(position) as balance,
  value(sum(position)) as market_value
WHERE account ~ "Assets:Crypto"
GROUP BY 1
ORDER BY market_value DESC

Performance Metrics

Track key performance indicators:

-- Total portfolio value query
SELECT
  sum(value(position)) as total_portfolio_value
WHERE account ~ "Assets:Crypto"
 
-- Realized gains/losses
SELECT
  sum(position) as realized_gains
WHERE account ~ "Income:CapitalGains:Crypto"

Staking and DeFi Integration

Staking Rewards Tracking

Record staking rewards with proper income recognition. The units arriving and the income recognized are the same event seen from two sides, so their weights must cancel: [units] × [price] of asset in, the same number of dollars of income out — and income is a credit, so it is negative.

2024-01-31 * "ETH Staking Rewards - January"
  Assets:Staking:Ethereum:ETH     0.08 ETH {2500.00 USD}
  Income:Crypto:Staking:ETH     -200.00 USD  ; 0.08 * 2,500.00
 
2024-01-31 * "ADA Staking Rewards - January"
  Assets:Staking:Cardano:ADA        25 ADA {0.50 USD}
  Income:Crypto:Staking:ADA      -12.50 USD  ; 25 * 0.50

The {2,500.00 USD} and {0.50 USD} are external valuations. Beancount stores whatever you write; it does not fetch a price or decide which quote is the right one. Pick a source — the exchange you would actually sell on, or a published index — and use it consistently, because that number is both your income and your future cost basis.

The reward's basis is recognized once

The $200.00 recognized above becomes the 0.08 ETH's cost basis. Selling it later realizes only the movement since the reward date:

2024-06-30 * "Sell the January ETH staking reward"
  Assets:Staking:Ethereum:ETH   -0.08 ETH {2500.00 USD} @ 3000.00 USD
  Assets:Crypto:Coinbase:USD     240.00 USD  ; gross proceeds, 0.08 * 3,000.00
  Income:CapitalGains:Crypto     -40.00 USD  ; 240.00 proceeds - 200.00 basis

$40.00 of capital gain, not $240.00 — the first $200.00 was already income in January. Booking the whole $240.00 as gain is the most common way reward income gets counted twice. When the reward becomes taxable, and at what rate, varies by jurisdiction; the ledger records receipt and disposal separately so either treatment can be reported from it.

DeFi Yield Tracking

Track complex DeFi positions:

Providing liquidity swaps two commodities for a third, so every leg has to weigh in the same currency before the transaction can balance. The ETH and LP legs already do, because they carry a cost; the stablecoin leg needs an explicit @ 1.00 USD, otherwise Beancount sees 25,000 units of USDC leaving and $25,000.00 of value arriving, which are different commodities and do not cancel.

2024-02-01 * "Uniswap LP Position"
  Assets:Crypto:Wallet:MetaMask:ETH    -10 ETH {2500.00 USD}
  Assets:Crypto:Wallet:MetaMask:USDC -25000 USDC @ 1.00 USD
  Assets:DeFi:Uniswap:ETH-USDC-LP      100 UNI-V2-ETH-USDC {500.00 USD}
 
2024-02-28 * "Uniswap LP Rewards - February"
  Assets:Crypto:Wallet:MetaMask:UNI    50 UNI {8.00 USD}
  Income:Crypto:DeFi:Yield          -400.00 USD  ; 50 * 8.00

Automated Portfolio Management

Importing exchange data

Beancount ships no exchange API client, and there is no configuration file that makes it talk to Coinbase or Binance. The supported path is an importer: you download the exchange's CSV export (or fetch it with your own script) and convert it to Beancount transactions with beangulp, the importer framework that succeeded Beancount 2's built-in beancount.ingest. beancount.ingest does not exist in Beancount 3.

If you do write a fetch script, its credentials file is yours alone — nothing in the Beancount toolchain reads it:

# Configuration for YOUR OWN fetch script. No Beancount tool reads this file.
exchanges:
  coinbase:
    api_key: "your_api_key"
    api_secret: "your_api_secret"
    passphrase: "your_passphrase"
  binance:
    api_key: "your_api_key"
    api_secret: "your_api_secret"

Automated Reconciliation

Set up automated balance verification:

; Balance assertions for automated verification
2024-01-31 balance Assets:Crypto:Coinbase:BTC    2.5 BTC
2024-01-31 balance Assets:Crypto:Binance:ETH    15.0 ETH
2024-01-31 balance Assets:Crypto:Kraken:ADA   1000.0 ADA

Those three numbers come from your exchange screens, not from this page, so the block above will not verify against anything until your own transactions are in the ledger. Two rules decide whether it verifies at all. A balance directive asserts the balance at the start of the named day, so an assertion dated 2024-01-31 does not see anything posted on 2024-01-31 — date it the day after the period you are closing. And every asserted account must be open, which is why Assets:Crypto:Kraken:ADA appears in the Basic Account Hierarchy section above.

Keeping prices up to date

Prices live in the ledger as price directives, and nothing in Beancount 3.2.3 fetches them for you. There is no custom "price-source" directive — a bare commodity symbol is not even a valid custom value, so that line is a syntax error — and no beancount.plugins.forecast module; the loader stops with an import error if you name it.

What does exist is beanprice, a separate package (pip install beanprice) providing the bean-price command. It reads a price: metadata field on each commodity directive, in the form "<quote currency>:<source module>/<ticker>", and writes price directives you append to your ledger:

1970-01-01 commodity BTC
  name: "Bitcoin"
  asset-class: "cryptocurrency"
  price: "USD:beanprice.sources.coinbase/BTC-USD"
 
1970-01-01 commodity ETH
  name: "Ethereum"
  asset-class: "cryptocurrency"
  price: "USD:beanprice.sources.coinbase/ETH-USD"
 
; What bean-price appends - and what you would otherwise type by hand
2024-01-15 price BTC 45000.00 USD
2024-01-15 price ETH 2500.00 USD

Any other metadata you attach to a commodity — sector, website, or the price-source field used earlier in this guide — is free-form documentation for your own benefit. The loader stores it and no tool acts on it.

Tax Optimization Strategies

Tax Loss Harvesting

Implement systematic tax loss harvesting:

; The lot being harvested, so this block stands on its own
2024-03-01 * "Buy ADA on Binance"
  Assets:Crypto:Binance:ADA       1000 ADA {0.60 USD}
  Assets:Crypto:Binance:USD    -600.00 USD
 
; Identify positions with unrealized losses
2024-12-15 * "Tax loss harvesting - Sell ADA at loss"
  Assets:Crypto:Binance:ADA      -1000 ADA {0.60 USD} @ 0.45 USD
  Assets:Crypto:Binance:USD       450.00 USD  ; gross proceeds
  Expenses:Crypto:Fees:Trading      5.00 USD
  Assets:Crypto:Binance:USD        -5.00 USD
  Expenses:CapitalLoss:Crypto     150.00 USD  ; 600.00 basis - 450.00 proceeds
 
; Repurchase after wash sale period (31 days)
2025-01-16 * "Repurchase ADA after wash sale period"
  Assets:Crypto:Binance:ADA      1000 ADA {0.45 USD}
  Assets:Crypto:Binance:USD     -450.00 USD
  Expenses:Crypto:Fees:Trading     5.00 USD
  Assets:Crypto:Binance:USD       -5.00 USD

Long-term vs Short-term Gains

Track holding periods for tax optimization:

; Use metadata to track purchase dates
2024-01-01 * "BTC Purchase - Long-term hold" ^long-term-btc
  Assets:Crypto:Coinbase:BTC  1.0 BTC {40000.00 USD}
  Assets:Crypto:Coinbase:USD -40000.00 USD
 
; Sell after one year for long-term capital gains treatment
2025-01-02 * "BTC Sale - Long-term capital gains" ^long-term-btc
  Assets:Crypto:Coinbase:BTC    -1.0 BTC {40000.00 USD} @ 55000.00 USD
  Assets:Crypto:Coinbase:USD   55000.00 USD
  Income:CapitalGains:LongTerm -15000.00 USD  ; 55,000.00 proceeds - 40,000.00 basis

The ^long-term-btc link on both entries is what lets you pull the purchase and the sale up together later; Beancount does not track holding periods itself.

Reporting and Analytics

Portfolio Summary Reports

Generate comprehensive portfolio reports:

-- Portfolio allocation by cryptocurrency
SELECT
  commodity,
  sum(position) as total_units,
  value(sum(position)) as market_value,
  value(sum(position)) / (
    SELECT value(sum(position))
    FROM positions
    WHERE account ~ "Assets:Crypto"
  ) * 100 as allocation_percentage
WHERE account ~ "Assets:Crypto"
GROUP BY commodity
ORDER BY market_value DESC;

Performance Analytics

Track portfolio performance over time:

-- Monthly portfolio performance
SELECT
  year(date) as year,
  month(date) as month,
  value(sum(position)) as portfolio_value
WHERE account ~ "Assets:Crypto"
GROUP BY year, month
ORDER BY year, month;

Income Analysis

Analyze income sources:

-- Income breakdown by source
SELECT
  account,
  sum(position) as total_income
WHERE account ~ "Income:Crypto"
GROUP BY account
ORDER BY total_income DESC;

Best Practices and Tips

1. Consistent Recording

  • Record transactions immediately after execution
  • Use standardized transaction descriptions
  • Include transaction hashes in metadata

2. Regular Reconciliation

  • Verify balances weekly across all platforms
  • Use balance assertions to catch discrepancies
  • Monitor for missing transactions

3. Backup and Security

  • Regularly backup your Beancount files
  • Use version control (Git) for change tracking
  • Encrypt sensitive data

4. Documentation

  • Document your account structure decisions
  • Maintain notes on complex transactions
  • Keep records of API configurations

5. Tax Preparation

  • Generate reports quarterly for tax planning
  • Maintain detailed records for audit purposes
  • Consult with tax professionals for complex situations

Conclusion

Effective cryptocurrency portfolio tracking requires precision, consistency, and the right tools. Beancount.io provides the flexibility and power needed to manage complex crypto portfolios across multiple exchanges, wallets, and DeFi protocols.

Key benefits of using Beancount.io for crypto portfolio tracking:

  • Complete Data Ownership: Your data in plain-text format
  • Precise Cost Basis: Lot-based tracking with specific identification
  • Unlimited Flexibility: Custom account structures and reporting
  • Tax Optimization: Advanced strategies for minimizing tax liability
  • Future-Proof: Open format ensures long-term accessibility

Start with a basic setup and gradually expand your tracking as your portfolio grows in complexity. The investment in proper setup will pay dividends in accurate reporting, tax optimization, and portfolio insights.

Ready to take control of your cryptocurrency portfolio? Get started with Beancount.io today.

Source: https://beancount.io/docs/Solutions/cryptocurrency-portfolio-tracking