Fetch.ai uAgents & The Graph drive Uniswap backtester: DataAgent fetches events, Backtester tests
This project implements a fully decentralized, agent-based backtesting system for Uniswap V4 strategies, leveraging Fetch.ai’s uAgents framework for orchestration and The Graph for efficient historical data access .
Architecture & Components At its core, the system comprises two autonomous uAgents running locally:
FetchEvents messages, then queries Uniswap’s V4 subgraph on The Graph to retrieve mint, burn, and swap events for a given pool and time range .BacktestRequest messages (specifying pool address, start/end UNIX timestamps, and optional strategy parameters), validates inputs, invokes the DataAgent to fetch events, writes a byte-for-byte compatible events.json file, and calls a Foundry-based Solidity backtester to simulate on-chain behavior .DataAgent Workflow
https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3, with exponential back-off and up to three retries using aiohttp .PoolEvent schema and sends an Events model payload back to the BacktestAgent .BacktestDataManager.cache_graph_data() layer to avoid redundant downloads when re-testing overlapping time windows .BacktestAgent Orchestration
BacktestRequest, shorthand pool symbols (e.g. “USDC-ETH”) are resolved to on-chain addresses.FetchEvents to the DataAgent and awaits an Events response.src/data/pool-events.json and executes the Foundry script src/UniV4Backtester.s.sol via forge script … --json, forking a live RPC at UNI_RPC_URL to ensure determinism .BacktestDataManager.save_backtest_result(), then replies with a typed BacktestResponse model back to the original requester .Message & Data Models
BacktestRequest: { pool: str; start: int; end: int; strategy_params?: dict }Events: { events: List<PoolEvent> }BacktestResponse: { kind: "backtest_result"; pnl: float; sharpe: float; total_fees: float; impermanent_loss: float; gas_costs: float; success: bool; error_message?: str }
All models extend uagents.Model (Pydantic v1) for validation and serialization .End-to-End Message Flow
flowchart TD
A[BacktestRequest] --> B[validate]
B --> C{cache?}
C -->|miss| D[DataAgent.fetch]
C -->|hit| E[load cache]
D --> F[save cache]
E & F --> G[run_backtest]
G --> H[store result]
H --> I[BacktestResponse]
Everything runs locally except for calls to The Graph and (optionally) 1inch .
Setup & Usage
pip install -r requirements.txt with uagents==0.22.5, Foundry (forge) for Solidity ≥0.8.26.export UNI_RPC_URL="your_rpc_url_here"python main.py to start DataAgent (port 8001) and BacktestAgent (port 8002); or launch individually via python data_agent.py / python backtest_agent.py .BacktestRequest and await BacktestResponse .Key Benefits
Extensibility & Roadmap
TheGraphClient to other DeFi subgraphs.Here’s a deep dive into how we built the Uniswap V4 backtester—everything from the low-level tech choices to the glue code that makes it all hum:
We leveraged Fetch.ai’s uAgents (v0.22.5) as our underlying RPC/message bus. Each agent is a standalone Python process:
data_agent.py) and BacktestAgent (backtest_agent.py) are both subclasses of uagents.Agent, exposing simple function-call endpoints over HTTP (ports 8001 and 8002).BacktestRequest, Events, BacktestResponse) extend uagents.Model (Pydantic v1) for strict validation/serialization .FetchEvents to DataAgent, which replies with an Events payload.DataAgent is responsible for fetching all mint/burn/swap events for a given Uniswap V4 pool:
https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3aiohttp with an exponential back-off and up to 3 retries on failures .PoolEvent schema and return as an Events model.BacktestDataManager.cache_graph_data() layer stores previously-fetched windows to avoid redundant downloads on overlapping backtests .The BacktestAgent drives an on-chain fork simulation using Foundry:
A Python wrapper (run_backtest) writes the JSON bundle to src/data/pool-events.json and shells out to:
forge script src/UniV4Backtester.s.sol \
--fork-url $UNI_RPC_URL \
--json
Foundry (Solidity ≥0.8.26) uses stdJson to ingest the events file byte-for-byte, replaying each mint/swap/burn on a forked RPC for exact reproducibility .
The wrapper captures stdout JSON (PnL, Sharpe, fees, impermanent loss, gas costs) and parses it back into our BacktestResponse model.
Ports & Endpoints
http://127.0.0.1:8001/submithttp://127.0.0.1:8002/submit Flow
BacktestRequest (pool address or shorthand like “USDC-ETH”, start/end UNIX timestamps, optional strategy params).BacktestDataManager.save_backtest_result().flowchart TD
A[BacktestRequest] --> B{Cache?}
B -->|miss| C[DataAgent.fetchEvents]
B -->|hit| D[Load cached events]
C & D --> E[run_backtest (Foundry)]
E --> F[Save & reply BacktestResponse]
stdJson library let us avoid writing a custom Solidity parser—just dump the same JSON the test harness expects, and replay it natively in solidity .This project stitches together:
The result is a fully local, extensible, and deterministic Uniswap V4 backtester—powered by agent-oriented design and on-chain tooling.

