Back to Hub
CREATED BY CHAINLINK LABS

Automated Portfolio Rebalancing

Automatically rebalance crypto portfolios by continuously monitoring allocation drift and executing portfolio adjustments when predefined thresholds are exceeded, while preserving the confidentiality of exchange API keys, LLM reasoning, portfolio allocation thresholds, and execution preferences.

What this template does

This CRE workflow implements a confidential portfolio rebalancing system for crypto allocations. It runs on a cron schedule, continuously monitors the drift between current and target asset weights, and automatically executes trades to restore the portfolio when a configurable threshold is exceeded. Exchange credentials, LLM reasoning, target allocations, and all execution preferences remain protected inside confidential execution.

Data flow:

  1. Fetch current portfolio state: holdings, asset prices, volatility index, and stablecoin reserve depth.
  2. Calculate per-asset allocation drift from configured target weights (BTC, ETH, USDC).
  3. If maximum drift across all assets is below the policy threshold, exit with NOOP and take no action.
  4. Send portfolio state and policy to an LLM reasoning model for trade selection.
  5. Reconcile LLM-proposed trades against policy constraints: venue overrides and slippage caps take precedence over LLM suggestions.
  6. Reject any trade that would breach the reserve floor. Cap oversized trades and split them into chunks.
  7. Separate trades into on-chain and off-chain execution routes and submit the full plan to the execute API.

Policy Constraints and Risk Flags

Policy constraints

All policy values are loaded from secrets at runtime and enforced before any trade is submitted to the execute API.

ConstraintDescription
Target allocationsTarget weight for each asset as a decimal (e.g., 0.5 = 50% BTC). All three values must sum to 1.0.
Drift thresholdMinimum drift percentage that triggers a rebalance. The workflow exits with NOOP if no asset exceeds this.
Maximum trade sizePer-chunk trade size cap in USD. Trades above this are split into equal-sized chunks.
Reserve floorMinimum USDC that must remain after all buy trades. Buy capacity is reduced to protect the floor; trades that breach it are rejected.
Slippage limitMaximum acceptable slippage in basis points. LLM-proposed slippage values are clamped to this ceiling.
Trade orderingSequence in which trades execute: sells-first, buys-first, or model-order
Preferred venuesComma-separated venue priority list used when the LLM does not specify a venue

Drift monitoring

The workflow computes allocation drift for each asset and triggers a rebalance when any asset exceeds the threshold:

currentWeight = assetValueUsd / totalPortfolioValueUsd
drift = |currentWeight − targetWeight| × 100

If maxDrift < driftThresholdPct across all assets, the workflow exits with NOOP. Otherwise, it builds sell orders for overweight assets and buy orders for underweight assets.

Trade execution guardrails

  • Reserve floor: Buy capacity is computed as stablecoinReserve − reserveFloor + totalPlannedSellUsd. Trades that would cause the reserve to drop below the floor are rejected.
  • Slippage: Each trade's slippage is clamped to min(llmProposed, maxSlippageBps).
  • Trade chunking: Trades larger than maxTradeUsd are split into chunks of up to maxTradeUsd each (the final chunk may be smaller), tracked with chunkIndex and chunkCount.
  • LLM reconciliation: LLM-proposed trade symbols and directions must align with policy-computed targets. Policy venue and slippage values override LLM suggestions when they conflict.

Prerequisites

  • Bun runtime
  • CRE CLI (cre)
  • An exchange or portfolio API that exposes current holdings, asset prices, and reserve data
  • An LLM API endpoint for reasoning (e.g., an OpenAI-compatible endpoint)

Configuration

The workflow ships with two config files:

  • automated-portfolio-rebalancing-ts/config.staging.json (TypeScript) and automated-portfolio-rebalancing-go/config.staging.json (Go): targets mock server endpoints
  • automated-portfolio-rebalancing-ts/config.production.json (TypeScript) and automated-portfolio-rebalancing-go/config.production.json (Go): same structure with empty URLs for you to populate

Key fields:

FieldDescription
scheduleCron expression. Default: 0 */5 * * * * (every 5 minutes)
mock_base_urlBase URL for the exchange or portfolio API
openai_urlLLM reasoning endpoint
openai_modelModel identifier (e.g., gpt-4.1-mini)
secrets_ids.*Secret IDs for API keys and all policy parameters

Secrets

Copy .env.example to .env and populate all values before running locally. The workflow enforces a strict limit of exactly 11 secrets and 5 HTTP calls per invocation.

Environment variableSecret IDPurpose
MOCK_EXCHANGE_API_KEYexchange_api_keyAuthenticates exchange API requests
MOCK_OPENAI_API_KEYopenai_api_keyAuthenticates LLM reasoning API requests
MOCK_REBALANCING_TARGET_ALLOCATION_BTC_PCTrebalancing_target_allocation_btc_pctTarget BTC weight as a decimal (e.g., 0.5)
MOCK_REBALANCING_TARGET_ALLOCATION_ETH_PCTrebalancing_target_allocation_eth_pctTarget ETH weight as a decimal (e.g., 0.3)
MOCK_REBALANCING_TARGET_ALLOCATION_USDC_PCTrebalancing_target_allocation_usdc_pctTarget USDC weight as a decimal (e.g., 0.2)
MOCK_REBALANCING_DRIFT_THRESHOLD_PCTrebalancing_drift_threshold_pctDrift percentage that triggers rebalancing (e.g., 5)
MOCK_REBALANCING_MAX_TRADE_USDrebalancing_max_trade_usdMaximum USD per trade chunk (e.g., 5000)
MOCK_REBALANCING_RESERVE_FLOOR_USDCrebalancing_reserve_floor_usdcMinimum USDC to keep in reserve (e.g., 2000)
MOCK_REBALANCING_MAX_SLIPPAGE_BPSrebalancing_max_slippage_bpsMaximum slippage in basis points (e.g., 50)
MOCK_REBALANCING_PREFERRED_VENUESrebalancing_preferred_venuesComma-separated venue list (e.g., binance,coinbase,onchain)
MOCK_REBALANCING_ORDER_SEQUENCE_PREFERENCErebalancing_order_sequence_preferencemodel-order, sells-first, or buys-first
CRE_ETH_PRIVATE_KEY(framework-level)Optional for local simulation. Required for on-chain swap execution.

Quick start

TypeScript

Run all commands from the automated-portfolio-rebalancing directory (the project root).

  1. Install dependencies

    cd automated-portfolio-rebalancing-ts && bun install && cd ..
    
  2. Create environment file

    cp .env.example .env
    
  3. Start mock server

    cd automated-portfolio-rebalancing-ts && bun run mock:server
    
  4. In another terminal, run checks and simulate

    cd automated-portfolio-rebalancing-ts
    bun run typecheck
    bun run test
    cd .. && cre workflow simulate ./automated-portfolio-rebalancing-ts --project-root ./ --target=staging-settings --env ./.env
    

Go

Run all commands from the automated-portfolio-rebalancing directory (the project root).

  1. Create environment file

    cp .env.example .env
    
  2. Start the mock server from the TypeScript directory (requires Node or Bun)

    cd automated-portfolio-rebalancing-ts && bun run mock:server
    
  3. In another terminal, run checks and simulate

    cd automated-portfolio-rebalancing-go
    go vet ./...
    go test ./...
    cd .. && cre workflow simulate ./automated-portfolio-rebalancing-go --project-root ./ --target=staging-settings --env ./.env
    

Production checklist

  • Populate config.production.json with real mock_base_url and openai_url.
  • Register all 11 secrets in the CRE secrets manager and verify secret IDs match those defined in config.production.json under secrets_ids.
  • Confirm target allocation secrets sum to 1.0 across BTC, ETH, and USDC before registering.
  • Set MOCK_REBALANCING_RESERVE_FLOOR_USDC to a value that provides a meaningful liquidity buffer for your portfolio size.
  • Set CRE_ETH_PRIVATE_KEY if on-chain swap routes are enabled.
  • Confirm the exchange API returns the expected portfolio state schema before deploying.
  • Run bun run typecheck && bun run test (TypeScript) or go vet ./... && go test ./... (Go) before registering.
  • Run cre workflow simulate against production endpoints to validate end-to-end behavior.
  • Register the workflow:
    • TypeScript: cre workflow register ./automated-portfolio-rebalancing-ts --project-root ./ --target=production-settings
    • Go: cre workflow register ./automated-portfolio-rebalancing-go --project-root ./ --target=production-settings

Troubleshooting

Workflow exits with NOOP

This is expected behavior when allocation drift is below the configured threshold. Check current versus target allocations and compare against MOCK_REBALANCING_DRIFT_THRESHOLD_PCT. Lower the threshold value to trigger rebalancing more frequently.

Reserve floor breach error

If you see trade X breaches reserve floor: projected Y < floor Z, a planned buy trade would consume too much of the stablecoin reserve. Decrease MOCK_REBALANCING_RESERVE_FLOOR_USDC, reduce MOCK_REBALANCING_MAX_TRADE_USD, or reduce the proportion of buy trades.

Config validation error

If you see config requires schedule, mock_base_url, openai_url, and openai_model, one of those fields is missing from the active config file. Verify that all four fields are populated in config.production.json (or config.staging.json).

LLM response format mismatch

If the LLM response cannot be parsed, the model must return an output_text field containing a JSON object with shouldRebalance, reasoning, and trades fields. Check that your LLM endpoint returns this structure and that the API key is valid.

HTTP request failures

If you see request failed status=4XX, confirm the mock server is running and that MOCK_EXCHANGE_API_KEY and MOCK_OPENAI_API_KEY in .env match the values expected by the mock server.

Secrets must be finite numbers

If you see secret X must be a finite number, one of the numeric policy secrets was set to a non-numeric value. Confirm that all numeric environment variables in .env are valid floats or integers.

Get the latest Chainlink content straight to your inbox.