Development of Crypto Fund Portfolio Rebalancing System
Managing crypto fund portfolio is not simply "buy/sell at market". Between target asset weights and trade execution stands a whole set of problems: slippage on illiquid assets, tax consequences of each trade, MEV attacks, investment mandate compliance, audit trail. Fund rebalancing system is not trading bot, it's back-office infrastructure with strict reliability and transparency requirements.
Task Decomposition
Rebalancing system consists of several independent subsystems to design separately:
- Portfolio state engine — tracks current positions from all sources (on-chain, CEX, DeFi positions in protocols)
- Rebalancing trigger — determines when rebalancing is needed
- Order computation engine — computes minimal set of trades to reach target weights
- Execution layer — executes trades optimally in terms of price and market impact
- Risk & compliance layer — pre-execution checks
- Audit trail — complete history of all decisions and executions
Portfolio State Engine
First and most underestimated part. Fund holds assets in different places — this is fundamental complexity.
Position data sources:
| Source | Method | Latency |
|---|---|---|
| On-chain ERC-20 balances | eth_call on balanceOf(address) |
Block (~12s) |
| Uniswap V3 LP positions | NonfungiblePositionManager.positions(tokenId) |
Block |
| Aave/Compound lending | aToken.balanceOf / borrowBalanceStored |
Block |
| Binance/Coinbase CEX | REST API with HMAC signature | <1s |
| Staking (Lido, validators) | Beacon Chain API + stETH balance | Block |
DeFi positions are most complex. Liquidity position in Uniswap V3 is not just "X ETH + Y USDC tokens". Real amounts depend on current price and range: if price moved out of range — position is 100% in one asset. Need to calculate honestly via SDK:
import { Pool, Position } from '@uniswap/v3-sdk';
import { Token, CurrencyAmount } from '@uniswap/sdk-core';
function getLiquidityAmounts(position: Position, currentPrice: Price) {
const { amount0, amount1 } = position.mintAmounts;
// Returns real amounts accounting for current tick
return { token0Amount: amount0, token1Amount: amount1 };
}
Position pricing. To calculate weights need prices in USD. Problems:
- Illiquid tokens: spot price from thin book not representative. Use TWAP (Uniswap V3 30-min TWAP) or geometric mean of multiple DEX prices
- Yield-bearing assets (stETH, aUSDC): account for accumulated yield
- LP positions: impermanent loss changes effective value
Rebalancing Triggers
Threshold-based — rebalance when asset weight deviates from target by >X%. Example: ETH target = 40%, rebalance at weight < 36% or > 44%. Simple but may generate many small trades in volatile market.
Periodic — rebalance every N days/weeks regardless of drift. Predictable for taxes and operational overhead.
Hybrid — rebalance at threshold crossing, but not more than once per X days. Most practical in practice.
Cost-aware rebalancing. Rebalancing costs money: gas, spread, slippage, profit taxes (in some jurisdictions). Mathematically should rebalance only if expected improvement in risk-adjusted returns from restoring target weights exceeds trade costs:
def should_rebalance(current_weights, target_weights, trade_costs) -> bool:
# Tracking error = sqrt(sum((w_i - t_i)^2))
tracking_error = np.sqrt(np.sum((current_weights - target_weights) ** 2))
estimated_trade_volume = compute_trade_volume(current_weights, target_weights)
total_cost = estimated_trade_volume * trade_costs.avg_cost_bps / 10000
# Rebalance if drift is large relative to cost
return tracking_error > THRESHOLD and tracking_error / total_cost > MIN_BENEFIT_COST_RATIO
Order Computation Engine
Goal: find minimal set of trades moving portfolio from current weights to target with minimal transaction costs.
Naive approach: compute delta for each asset and trade pair with USDC/USDT. Problem: creates unnecessary trades. If reducing ETH and increasing BTC — why do ETH→USDC and USDC→BTC? Better find direct ETH→BTC pair if liquid market exists.
Optimization via flow network:
import networkx as nx
from scipy.optimize import linprog
def optimize_rebalance_trades(current_usd, target_usd, available_pairs):
"""
current_usd: dict {token: usd_value}
target_usd: dict {token: usd_value}
available_pairs: list[(token_a, token_b, cost_bps)]
"""
deltas = {t: target_usd[t] - current_usd.get(t, 0) for t in target_usd}
# Build trade pair graph, find min cost flow
G = nx.DiGraph()
for a, b, cost in available_pairs:
G.add_edge(a, b, weight=cost)
G.add_edge(b, a, weight=cost)
# Solve as transport problem
# ... (LP solver)
return optimal_trades
Market impact accounting. Large trades move price against. Fund with $10M+ AUM cannot execute as single order. Market impact models:
-
Square-root model:
impact = σ * sqrt(Q / ADV), where ADV — average daily volume - Almgren-Chriss model — more accurate for TWAP execution
Execution Layer
CEX execution. For liquid assets on Binance/Coinbase: TWAP or VWAP orders via API. TWAP breaks large order into equal time slices, minimizing market impact.
async def execute_twap(exchange, symbol, side, total_qty, duration_minutes, slices=10):
slice_qty = total_qty / slices
interval = duration_minutes * 60 / slices
for i in range(slices):
order = await exchange.create_order(
symbol=symbol,
type='market', # or limit with small offset
side=side,
amount=slice_qty
)
log_execution(order, slice=i)
await asyncio.sleep(interval)
On-chain execution via aggregators. 1inch, Paraswap, 0x Protocol — aggregate DEX liquidity for better price:
import axios from 'axios';
import { ethers } from 'ethers';
async function executeSwapVia1inch(
tokenIn: string,
tokenOut: string,
amount: bigint,
slippageBps: number
) {
const quote = await axios.get(`https://api.1inch.dev/swap/v6.0/1/swap`, {
params: {
src: tokenIn,
dst: tokenOut,
amount: amount.toString(),
from: FUND_ADDRESS,
slippage: slippageBps / 100,
},
headers: { Authorization: `Bearer ${ONEINCH_API_KEY}` }
});
const tx = await signer.sendTransaction({
to: quote.data.tx.to,
data: quote.data.tx.data,
value: quote.data.tx.value,
});
return tx;
}
MEV protection for on-chain trades. Large fund swap transactions visible in mempool attract sandwich attacks. Solutions:
-
Flashbots Protect RPC (
https://rpc.flashbots.net) — transactions go to private mempool, hidden from MEV bots before block inclusion - MEV Blocker from CoW Protocol — similar functionality
- CoW Protocol (Coincidence of Wants) — batch auctions, built-in MEV protection
Risk & Compliance Layer
Mandatory checks before any trade execution:
class RiskChecks:
def pre_trade_checks(self, proposed_trades: list[Trade]) -> list[CheckResult]:
checks = [
self.check_position_limits(proposed_trades), # asset limits
self.check_concentration_risk(proposed_trades), # diversification
self.check_liquidity_impact(proposed_trades), # market impact
self.check_counterparty_limits(proposed_trades), # exchange limits
self.check_blacklisted_assets(proposed_trades), # sanctions lists
self.check_investment_mandate(proposed_trades), # mandate restrictions
]
return checks
Investment mandate enforcement. Funds operate with restrictions: "no more than 20% in single asset", "only assets with market cap > $500M", "no more than 30% in DeFi protocols". System must check mandate before execution and block violations.
Audit Trail and Reporting
Each rebalancing decision must be documented: what was trigger, what weights before/after, which trades executed, at what price, cost.
CREATE TABLE rebalancing_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
triggered_at TIMESTAMPTZ NOT NULL,
trigger_type VARCHAR(50), -- 'threshold', 'periodic', 'manual'
weights_before JSONB,
weights_target JSONB,
weights_after JSONB,
total_trade_volume_usd NUMERIC,
total_cost_usd NUMERIC,
execution_status VARCHAR(50),
trades JSONB -- array of executed trades
);
This is needed not just for internal control — auditors and regulators will demand complete trail.
Technology Stack and Architecture
Recommended stack:
| Component | Technology |
|---|---|
| Backend | Python (pandas/numpy for weight calculations, asyncio for parallel execution) |
| Scheduler | Apache Airflow or Prefect (DAG for rebalancing workflow) |
| Storage | PostgreSQL + TimescaleDB (time-series of positions) |
| CEX connections | CCXT (unified interface to 100+ exchanges) |
| On-chain | viem/ethers.js + Flashbots bundle |
| Monitoring | Grafana + Prometheus + PagerDuty alerts |
| Secrets | HashiCorp Vault for API keys and private keys |
Key security is critical. System manages real fund assets. Private keys — never in config files or container environment variables. Scheme: HSM (Hardware Security Module) or KMS (AWS KMS / GCP KMS) for key storage, sign transactions via secure enclave, multi-sig for operations above threshold.
Timeline and Phases
Phase 1 — Portfolio tracking (3-4 weeks). Aggregating positions from all sources, pricing, weight calculation. This is foundation of entire system.
Phase 2 — Rebalancing engine (2-3 weeks). Triggers, trade optimization, risk checks.
Phase 3 — Execution layer (3-4 weeks). CEX integrations via CCXT, on-chain execution with MEV protection, TWAP logic.
Phase 4 — Monitoring and compliance (2 weeks). Audit trail, reports, alerts, monitoring dashboard.
Total: 2.5-3.5 months for production-ready system. Acceleration possible by limiting number of supported position sources.







