Tax Loss Harvesting

Tax Loss Harvesting is a strategy used by portfolio managers and/or end-users to minimize tax liabilities by offsetting gains with losses. The goal is to lower overall taxable income and, consequently, to reduce the amount of taxes owed.

TaxBit's Inventory APIs enable digital asset platforms to build Tax Loss Harvesting and other Portfolio Optimization experiences within their web and mobile applications. The APIs support fetching current for an account with detailed lot information, such as the quantity, cost and acquisition date.

The unrealized gain or loss for each lot can be calculated using this inventory lot data and a price for a given asset. These calculated unrealized gains can then be displayed to the user in a portfolio performance or trading workflow experience to help improve trading decisions.

The rest of this guide describes the required data integration and API call sequences to build a hypothetical experience for a single platform end user.

Prerequisites and Requirements

  1. Transaction API Integration - Taxbit's Tax Loss Harvesting and portfolio optimization capabilities require an existing integration with our Transactions API. As transactions are sent from the digital asset platform to Taxbit, inventory and lot information are automatically updated.
  2. Asset Spot Price - The current asset spot price data for each asset must be available and provided by the platform. Each platform has its market pricing that changes in real-time and the end user should see estimated gain/loss calculations based on the pricing that matches what is otherwise visible on the platform.

API Call Sequence

  1. Get a user's inventory summaries
  2. Get lot-level data for each asset included in the summary
  3. Calculate unrealized gain/loss estimates by combining lot and pricing data

The guide assumes a tenant-scoped {{authToken}} and a {{userId}} have already been obtained using the Auth and User APIs.

1. Fetch Inventory Summary

Use the List Inventory Summary for all Assets API to list assets held in inventory for a given account.

The code below demonstrates the fetching of the inventory summary with the explicit filter out of any inventory lots with missing cost basis.

curl --request GET \
  --url 'https://api.multi1.enterprise.taxbit.com/v1/inventory/summaries?account_id={{account_id}}' \
  --header 'authorization: Bearer {{access_token}}'

The response body’s data field is a collection of summaries, one for each asset:

{
  "data": [
    {
      "asset": {
        "uuid": "9cd9f4d4-078b-4e44-a308-7662fec0f546",
        "code": "BTC",
        "name": "Bitcoin",
        "type": "Crypto"
      },
      "fiat_asset": {
        "uuid": "df939ab7-b7ed-4216-be63-ca1d2a130396",
        "code": "USD",
        "name": "United States Dollar",
        "type": "Fiat"
      },
      "summary": {
        "latest_transaction_datetime": "2024-01-03T00:00:00.000Z",
        "total_quantity": "5",
        "total_cost": "1200",
        "average_unit_cost": "240",
        "total_quantity_with_cost_basis": "5"
      },
    },
    {
      "asset": {
        "name": "Ethereum",
        "symbol": "Ξ",
        "code": "ETH",
        "type": "Crypto",
        "uuid": "b7a005b5-f4d5-44ea-ae80-d4f9e8313558"
      },
      "fiat_asset": {
        "uuid": "df939ab7-b7ed-4216-be63-ca1d2a130396",
        "code": "USD",
        "name": "United States Dollar",
        "type": "Fiat",
      },
      "summary": {
        "latest_transaction_datetime": "2024-01-03T00:00:00.000Z",
        "total_quantity": "15",
        "total_cost": "1500",
        "average_unit_cost": "300",
        "total_quantity_with_cost_basis": "15"
      }
    }
  ]
}

2. Fetch lot-level detail for each asset

Use the Inventory Summary by Asset API to fetch the inventory details for Bitcoin (using either the asset_id or asset_code query parameter):

curl --request GET \
  --url 'https://api.multi1.enterprise.taxbit.com/v1/inventory?account_id={{account_id}}&asset_code=BTC' \
  --header 'authorization: Bearer {{access_token}}'

The response body will include a collection of all BTC lots::

{
  "data": {
    "asset": {
      "uuid": "9cd9f4d4-078b-4e44-a308-7662fec0f546",
      "code": "BTC",
      "name": "Bitcoin",
      "type": "Crypto"
    },
    "lots": [
      {
        "id": "c090baaa-835d-53af-ae31-bb754cdaab27",
        "acquisition_datetime": "2024-01-01T00:00:00.000Z",
        "term": "long-term",
        "quantity": "5",
        "cost": "1200",
        "fiat_asset_code": "USD",
        "unit_cost": "240"
      }
    ],
    "fiat_asset": {
      "uuid": "df939ab7-b7ed-4216-be63-ca1d2a130396",
      "code": "USD",
      "name": "United States Dollar",
      "type": "Fiat"
    },
    "summary": {
      "latest_transaction_datetime": "2024-01-03T00:00:00.000Z",
      "total_quantity": "5",
      "total_cost": "1200",
      "average_unit_cost": "240",
      "total_quantity_with_cost_basis": "5"
    }
  }
}

Repeat the process for ETH.

3. Calculating Unrealized Gain/Loss

Given a collection of current asset prices and API responses, you can calculate aggregate gain/loss information for each lot. For example:

function calculateUnrealizedGainLoss(currentPrices, assetResponses) {
    return assetResponses.map(({ data }) => {
        const { asset, lots } = data;
        const currentPrice = currentPrices[asset.code];

        return {
            code: asset.code,
            name: asset.name,
            lots: lots.map((lot) => {
                const quantity = parseFloat(lot.quantity);
                const costBasis = parseFloat(lot.cost);
                const marketValue = quantity * currentPrice;
                const lotGainLoss = marketValue - costBasis;

                return {
                    ...lot,
                    marketValue,
                    gainLoss: lotGainLoss,
                };
            })
        }
    });
}

Usage example:

calculateUnrealizedGainLoss(
    { "BTC": 109000 },
    [{
        "data": {
            "asset": {
                "uuid": "9cd9f4d4-078b-4e44-a308-7662fec0f546",
                "code": "BTC",
                "name": "Bitcoin",
                "type": "Crypto"
            },
            "lots": [
                {
                    "id": "c090baaa-835d-53af-ae31-bb754cdaab27",
                    "acquisition_datetime": "2024-01-01T00:00:00.000Z",
                    "term": "long-term",
                    "quantity": "5",
                    "cost": "1200",
                    "fiat_asset_code": "USD",
                    "unit_cost": "240"
                }
            ],
            "fiat_asset": {
                "uuid": "df939ab7-b7ed-4216-be63-ca1d2a130396",
                "code": "USD",
                "name": "United States Dollar",
                "type": "Fiat"
            },
            "summary": {
                "latest_transaction_datetime": "2024-01-03T00:00:00.000Z",
                "total_quantity": "5",
                "total_cost": "1200",
                "average_unit_cost": "240",
                "total_quantity_with_cost_basis": "5"
            }
        }
    }]);
/*
Output: 

[{
  code: 'BTC',
  name: 'Bitcoin',
  lots: [
    {
      id: 'c090baaa-835d-53af-ae31-bb754cdaab27',
      acquisition_datetime: '2024-01-01T00:00:00.000Z',
      term: 'long-term',
      quantity: '5',
      cost: '1200',
      fiat_asset_code: 'USD',
      unit_cost: '240',
      marketValue: 545000,
      gainLoss: 543800
    }
  ]
}]
*/

🚧

Note: The guide ignores potential floating point precision issues.

Digital asset quantities can use up to 18 decimals of precision, so a BigDecimal type library should be used to parse the API responses.

Conclusion

This guide has demonstrated how to materialize the unrealized gain/loss amount for each lot in the user’s inventory. This data can be exposed on a portfolio performance table or embedded in a trade execution workflow to help users optimize their trading decisions.