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-v3
aiohttp
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/submit
http://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.