mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 20:26:41 +00:00
Add AI Agent for Autonomous DeFi Trading prototype
This commit introduces a production-inspired prototype for autonomous DeFi trading that combines off-chain AI intelligence with on-chain execution. Key Components: 1. Off-Chain AI Planner (Python + PyTorch) - Q-learning neural network for trading decisions - Reinforcement learning from simulated/real price data - Three actions: Buy, Hold, Sell - Configurable learning parameters (learning rate, gamma, epsilon) 2. On-Chain Execution Layer (Solidity) - SimpleDEX: Secure DEX contract for ETH <-> Token swaps - MockERC20: Test token for development - Security features: daily limits, min/max constraints, rate checks - Emergency controls and liquidity management 3. Blockchain Integration (Web3.py) - Connects AI decisions to blockchain execution - Transaction signing and submission - Balance tracking and price monitoring - Gas optimization and retry logic 4. Hybrid Architecture - AI planning happens off-chain for flexibility - All trades executed on-chain for transparency - Separation of concerns: perception, planning, action - Production-ready patterns inspired by real DeFi agents Features: - Multi-mode operation: Monitor (watch), Paper (simulate), Live (execute) - Risk management: stop-loss, take-profit, daily limits - Comprehensive configuration system - Model persistence (save/load trained agents) - Real-time performance monitoring Structure: ai-defi-agent/ ├── contracts/ # Solidity smart contracts │ ├── SimpleDEX.sol # DEX implementation │ └── MockERC20.sol # Test token ├── src/ # Python source code │ ├── ai_agent.py # Q-learning agent │ ├── blockchain_connector.py # Web3 integration │ ├── config.py # Configuration │ └── trading_agent.py # Main orchestrator ├── tests/ # Unit tests ├── README.md # Comprehensive documentation ├── DEPLOYMENT.md # Deployment guide ├── demo.py # Interactive demo └── requirements.txt # Python dependencies This prototype demonstrates hybrid AI-blockchain architecture suitable for educational purposes and can be extended for production use with proper security audits, oracle integration, and multi-sig controls.
This commit is contained in:
parent
127d1f42bb
commit
97e3564448
13 changed files with 2637 additions and 0 deletions
40
ai-defi-agent/.env.example
Normal file
40
ai-defi-agent/.env.example
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# AI DeFi Trading Agent Configuration
|
||||
# Copy this file to .env and fill in your values
|
||||
# WARNING: Never commit .env file with real private keys!
|
||||
|
||||
# ============================================
|
||||
# BLOCKCHAIN CONNECTION
|
||||
# ============================================
|
||||
# Your local Ethereum node or testnet RPC URL
|
||||
ETH_RPC_URL=http://localhost:8545
|
||||
|
||||
# Chain ID (1337 for local, 1 for mainnet, 11155111 for Sepolia)
|
||||
CHAIN_ID=1337
|
||||
|
||||
# ============================================
|
||||
# WALLET CONFIGURATION
|
||||
# ============================================
|
||||
# Private key for the AI agent wallet (with 0x prefix)
|
||||
# SECURITY: Keep this secret! Use a dedicated wallet with limited funds
|
||||
AGENT_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# ============================================
|
||||
# DEPLOYED CONTRACTS
|
||||
# ============================================
|
||||
# SimpleDEX contract address (fill after deployment)
|
||||
DEX_ADDRESS=0x0000000000000000000000000000000000000000
|
||||
|
||||
# MockERC20 token address (fill after deployment)
|
||||
TOKEN_ADDRESS=0x0000000000000000000000000000000000000000
|
||||
|
||||
# ============================================
|
||||
# OPTIONAL: CUSTOM ABI PATHS
|
||||
# ============================================
|
||||
# Uncomment if you have custom ABI files
|
||||
# DEX_ABI_PATH=contracts/abi/SimpleDEX.json
|
||||
# TOKEN_ABI_PATH=contracts/abi/MockERC20.json
|
||||
|
||||
# ============================================
|
||||
# LOGGING
|
||||
# ============================================
|
||||
LOG_LEVEL=INFO
|
||||
64
ai-defi-agent/.gitignore
vendored
Normal file
64
ai-defi-agent/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
|
||||
# PyTorch models
|
||||
models/*.pth
|
||||
*.pth
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Jupyter
|
||||
.ipynb_checkpoints
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Contract artifacts (if using Hardhat/Foundry)
|
||||
artifacts/
|
||||
cache/
|
||||
out/
|
||||
broadcast/
|
||||
|
||||
# Sensitive data
|
||||
.env
|
||||
*.key
|
||||
wallet.json
|
||||
keystore/
|
||||
240
ai-defi-agent/DEPLOYMENT.md
Normal file
240
ai-defi-agent/DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
# Deployment Guide for AI DeFi Trading Agent
|
||||
|
||||
This guide walks you through deploying and running the AI DeFi Trading Agent on your cloned Ethereum setup.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Running Ethereum Node**: Your cloned go-ethereum node must be running
|
||||
```bash
|
||||
# Start your local Ethereum node (from go-ethereum root)
|
||||
./build/bin/geth --dev --http --http.api eth,web3,personal,net --http.corsdomain "*"
|
||||
```
|
||||
|
||||
2. **Python Environment**: Python 3.8+ with pip
|
||||
```bash
|
||||
python3 --version # Should be 3.8 or higher
|
||||
```
|
||||
|
||||
3. **Funded Wallet**: A wallet with some ETH for gas fees and trading
|
||||
|
||||
## Step 1: Environment Setup
|
||||
|
||||
### 1.1 Create Python Virtual Environment
|
||||
|
||||
```bash
|
||||
cd ai-defi-agent
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
```
|
||||
|
||||
### 1.2 Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 1.3 Configure Environment Variables
|
||||
|
||||
```bash
|
||||
# Copy the example environment file
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env with your values
|
||||
nano .env # or use your preferred editor
|
||||
```
|
||||
|
||||
**Important**: Set at least these values in `.env`:
|
||||
- `ETH_RPC_URL`: Your node URL (e.g., `http://localhost:8545`)
|
||||
- `AGENT_PRIVATE_KEY`: Your wallet's private key (with 0x prefix)
|
||||
|
||||
## Step 2: Deploy Smart Contracts
|
||||
|
||||
You can deploy using Remix, Hardhat, or Foundry. Here's how to do it with Remix (easiest for testing):
|
||||
|
||||
### 2.1 Using Remix IDE
|
||||
|
||||
1. **Open Remix**: Go to https://remix.ethereum.org
|
||||
|
||||
2. **Create Files**:
|
||||
- Create `MockERC20.sol` and paste content from `contracts/MockERC20.sol`
|
||||
- Create `SimpleDEX.sol` and paste content from `contracts/SimpleDEX.sol`
|
||||
|
||||
3. **Compile**:
|
||||
- Select Solidity Compiler (0.8.x)
|
||||
- Compile both contracts
|
||||
|
||||
4. **Deploy MockERC20**:
|
||||
- Go to "Deploy & Run Transactions"
|
||||
- Select "Injected Provider - MetaMask" or "Web3 Provider" (http://localhost:8545)
|
||||
- Deploy `MockERC20` with parameters:
|
||||
- `_name`: "Mock USDC"
|
||||
- `_symbol`: "mUSDC"
|
||||
- `_decimals`: 18
|
||||
- `_initialSupply`: 1000000000000000000000000 (1 million tokens)
|
||||
- **Copy the deployed token address**
|
||||
|
||||
5. **Deploy SimpleDEX**:
|
||||
- Deploy `SimpleDEX` with parameters:
|
||||
- `_token`: [paste MockERC20 address]
|
||||
- `_initialRate`: 1000000000000000000000 (1000 tokens per ETH)
|
||||
- **Copy the deployed DEX address**
|
||||
|
||||
6. **Add Liquidity**:
|
||||
- Call `approve` on MockERC20 with:
|
||||
- `spender`: [SimpleDEX address]
|
||||
- `amount`: 500000000000000000000000 (500k tokens)
|
||||
- Call `addLiquidity` on SimpleDEX with:
|
||||
- `tokenAmount`: 500000000000000000000000 (500k tokens)
|
||||
- Send some ETH to SimpleDEX using the "Value" field and "Transact" button
|
||||
|
||||
### 2.2 Update Configuration
|
||||
|
||||
Update your `.env` file with the deployed addresses:
|
||||
|
||||
```bash
|
||||
DEX_ADDRESS=0x... # SimpleDEX address from Remix
|
||||
TOKEN_ADDRESS=0x... # MockERC20 address from Remix
|
||||
```
|
||||
|
||||
## Step 3: Train the AI Agent (Optional)
|
||||
|
||||
The agent will auto-train on first run, but you can pre-train:
|
||||
|
||||
```bash
|
||||
cd src
|
||||
python ai_agent.py
|
||||
```
|
||||
|
||||
This creates a trained model in `models/trading_agent.pth`.
|
||||
|
||||
## Step 4: Run the Agent
|
||||
|
||||
The agent has three modes:
|
||||
|
||||
### Monitor Mode (Read-Only)
|
||||
Just watches prices and shows what it would do:
|
||||
```bash
|
||||
cd src
|
||||
python trading_agent.py monitor
|
||||
```
|
||||
|
||||
### Paper Trading Mode
|
||||
Simulates trades without executing them:
|
||||
```bash
|
||||
python trading_agent.py paper
|
||||
```
|
||||
|
||||
### Live Trading Mode
|
||||
⚠️ **Executes real trades on-chain**:
|
||||
```bash
|
||||
python trading_agent.py live
|
||||
```
|
||||
|
||||
## Step 5: Monitor Performance
|
||||
|
||||
The agent will display:
|
||||
- Current market state (price, rate)
|
||||
- AI decisions (buy/hold/sell)
|
||||
- Trade execution results
|
||||
- Performance metrics every 5 iterations
|
||||
|
||||
Example output:
|
||||
```
|
||||
--- Iteration 1 @ 14:30:25 ---
|
||||
Current rate: 1000.00 tokens/ETH
|
||||
Price delta: -0.0231 (-2.31%)
|
||||
Balances - ETH: 10.0000, Token: 0.0000
|
||||
AI Decision: BUY
|
||||
|
||||
=== Executing BUY Trade ===
|
||||
Swapping 0.1 ETH for tokens...
|
||||
Current rate: 1000.0 tokens/ETH
|
||||
Expected to receive: 100.0000 tokens
|
||||
Transaction sent: 0xabc123...
|
||||
✓ Trade successful!
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Issues
|
||||
```bash
|
||||
# Check if your node is running
|
||||
curl http://localhost:8545 -X POST -H "Content-Type: application/json" \
|
||||
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
|
||||
```
|
||||
|
||||
### Contract Deployment Fails
|
||||
- Ensure your wallet has enough ETH
|
||||
- Check that you're connected to the right network
|
||||
- Verify Solidity compiler version (0.8.x)
|
||||
|
||||
### Transaction Failures
|
||||
- Check gas prices in `config.py`
|
||||
- Verify DEX has enough liquidity
|
||||
- Ensure agent wallet has ETH for gas
|
||||
|
||||
### Import Errors
|
||||
```bash
|
||||
# Reinstall dependencies
|
||||
pip install -r requirements.txt --upgrade
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Private Keys**: Never commit `.env` or share private keys
|
||||
2. **Test First**: Always test on a local/testnet before mainnet
|
||||
3. **Limited Funds**: Use a dedicated wallet with limited funds
|
||||
4. **Monitor**: Watch the agent closely during initial runs
|
||||
5. **Risk Limits**: Set appropriate `STOP_LOSS` and `MAX_TRADE_AMOUNT`
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
Edit `src/config.py` to customize:
|
||||
- Trade amounts and limits
|
||||
- Risk management thresholds
|
||||
- AI learning parameters
|
||||
- Gas price settings
|
||||
- Monitoring intervals
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Monitor Performance**: Run in paper mode for 24 hours
|
||||
2. **Optimize Parameters**: Adjust thresholds based on results
|
||||
3. **Enhance AI**: Add more sophisticated features (LSTM, sentiment analysis)
|
||||
4. **Production Setup**:
|
||||
- Use Chainlink oracles for real price feeds
|
||||
- Add multi-sig for security
|
||||
- Implement ERC-4337 account abstraction
|
||||
- Deploy monitoring/alerting
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Check balances
|
||||
python -c "from blockchain_connector import BlockchainConnector; \
|
||||
from config import Config; \
|
||||
bc = BlockchainConnector(Config.RPC_URL, Config.PRIVATE_KEY, \
|
||||
Config.DEX_ADDRESS, Config.TOKEN_ADDRESS); \
|
||||
print(bc.get_balances())"
|
||||
|
||||
# Get DEX stats
|
||||
python -c "from blockchain_connector import BlockchainConnector; \
|
||||
from config import Config; \
|
||||
bc = BlockchainConnector(Config.RPC_URL, Config.PRIVATE_KEY, \
|
||||
Config.DEX_ADDRESS, Config.TOKEN_ADDRESS); \
|
||||
print(bc.get_dex_stats())"
|
||||
|
||||
# Check current rate
|
||||
python -c "from blockchain_connector import BlockchainConnector; \
|
||||
from config import Config; \
|
||||
bc = BlockchainConnector(Config.RPC_URL, Config.PRIVATE_KEY, \
|
||||
Config.DEX_ADDRESS, Config.TOKEN_ADDRESS); \
|
||||
print(f'Rate: {bc.get_current_rate()} tokens/ETH')"
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For issues:
|
||||
1. Check the logs in `logs/trading_agent.log`
|
||||
2. Review contract transactions on your block explorer
|
||||
3. Verify configuration in `.env` and `config.py`
|
||||
431
ai-defi-agent/README.md
Normal file
431
ai-defi-agent/README.md
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
# AI Agent for Autonomous DeFi Trading
|
||||
|
||||
A production-inspired prototype that combines **off-chain AI intelligence** with **on-chain execution** for autonomous DeFi trading. This hybrid architecture uses reinforcement learning (Q-learning) for decision-making while maintaining transparency and security through smart contract execution.
|
||||
|
||||
## Overview
|
||||
|
||||
This project demonstrates a practical implementation of an AI-powered trading agent that:
|
||||
- 📊 **Monitors** token prices via on-chain exchange rates (simulated oracle)
|
||||
- 🤖 **Decides** on trades using a Q-learning neural network
|
||||
- ⚡ **Executes** swaps transparently on a custom DEX smart contract
|
||||
- 🔒 **Manages risk** with configurable limits and circuit breakers
|
||||
|
||||
### Hybrid Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ AI DeFi Trading Agent │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ OFF-CHAIN (AI) │ │ ON-CHAIN (Smart │
|
||||
│ │ │ Contracts) │
|
||||
│ • Q-Learning Model │ │ │
|
||||
│ • Price Analysis │◄────────────►│ • SimpleDEX │
|
||||
│ • Decision Making │ Web3.py │ • ERC20 Token │
|
||||
│ • Risk Management │ │ • Liquidity Pools │
|
||||
└──────────────────────┘ └──────────────────────┘
|
||||
Python + PyTorch Solidity + EVM
|
||||
```
|
||||
|
||||
**Why Hybrid?**
|
||||
- **Flexibility**: AI logic can be updated without contract redeployment
|
||||
- **Transparency**: All trades executed on-chain with full auditability
|
||||
- **Security**: Smart contracts enforce limits and prevent manipulation
|
||||
- **Efficiency**: Complex ML computations happen off-chain to save gas
|
||||
|
||||
## Features
|
||||
|
||||
### ✨ Core Capabilities
|
||||
|
||||
- **Reinforcement Learning**: Q-learning agent that learns optimal trading strategies
|
||||
- **Multi-Mode Operation**: Monitor (watch-only), Paper (simulate), Live (execute)
|
||||
- **Risk Management**: Daily limits, stop-loss, take-profit, and sanity checks
|
||||
- **Real-Time Monitoring**: Continuous price tracking and portfolio analysis
|
||||
- **Production-Ready Contracts**: SimpleDEX with security features and gas optimization
|
||||
- **Comprehensive Logging**: Track all decisions, trades, and performance metrics
|
||||
|
||||
### 🛡️ Security Features
|
||||
|
||||
- Daily trading volume limits
|
||||
- Min/max swap amount constraints
|
||||
- Rate change restrictions (prevents oracle manipulation)
|
||||
- Emergency withdrawal mechanism
|
||||
- Configurable gas limits
|
||||
- Private key environment isolation
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
ai-defi-agent/
|
||||
├── README.md # This file
|
||||
├── DEPLOYMENT.md # Deployment guide
|
||||
├── requirements.txt # Python dependencies
|
||||
├── .env.example # Environment configuration template
|
||||
├── .gitignore # Git ignore rules
|
||||
│
|
||||
├── contracts/ # Solidity smart contracts
|
||||
│ ├── SimpleDEX.sol # DEX for token swaps
|
||||
│ └── MockERC20.sol # Test token (mock USDC)
|
||||
│
|
||||
├── src/ # Python source code
|
||||
│ ├── ai_agent.py # Q-learning trading agent
|
||||
│ ├── blockchain_connector.py # Web3.py integration
|
||||
│ ├── config.py # Configuration management
|
||||
│ └── trading_agent.py # Main agent orchestrator
|
||||
│
|
||||
├── tests/ # Test files (future)
|
||||
│ └── test_agent.py
|
||||
│
|
||||
├── models/ # Trained AI models (created at runtime)
|
||||
│ └── trading_agent.pth
|
||||
│
|
||||
└── logs/ # Application logs (created at runtime)
|
||||
└── trading_agent.log
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Prerequisites
|
||||
|
||||
- **Ethereum Node**: Running go-ethereum node (local or testnet)
|
||||
- **Python**: 3.8 or higher
|
||||
- **Wallet**: Funded with ETH for gas fees
|
||||
|
||||
### 2. Installation
|
||||
|
||||
```bash
|
||||
# Navigate to the project directory
|
||||
cd ai-defi-agent
|
||||
|
||||
# Create and activate virtual environment
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. Configuration
|
||||
|
||||
```bash
|
||||
# Copy environment template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit with your values
|
||||
nano .env
|
||||
```
|
||||
|
||||
Required variables:
|
||||
- `ETH_RPC_URL`: Your Ethereum node URL (e.g., `http://localhost:8545`)
|
||||
- `AGENT_PRIVATE_KEY`: Private key for the agent wallet
|
||||
- `DEX_ADDRESS`: SimpleDEX contract address (after deployment)
|
||||
- `TOKEN_ADDRESS`: MockERC20 token address (after deployment)
|
||||
|
||||
### 4. Deploy Contracts
|
||||
|
||||
See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed deployment instructions.
|
||||
|
||||
Quick deploy using Remix:
|
||||
1. Open https://remix.ethereum.org
|
||||
2. Deploy `MockERC20.sol` (1M tokens, 18 decimals)
|
||||
3. Deploy `SimpleDEX.sol` (with token address, rate 1000)
|
||||
4. Add liquidity to DEX
|
||||
5. Update `.env` with deployed addresses
|
||||
|
||||
### 5. Run the Agent
|
||||
|
||||
**Monitor Mode** (watch only, no trades):
|
||||
```bash
|
||||
cd src
|
||||
python trading_agent.py monitor
|
||||
```
|
||||
|
||||
**Paper Trading Mode** (simulate trades):
|
||||
```bash
|
||||
python trading_agent.py paper
|
||||
```
|
||||
|
||||
**Live Trading Mode** (execute real trades):
|
||||
```bash
|
||||
python trading_agent.py live
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### 1. Perception (Price Monitoring)
|
||||
|
||||
The agent continuously monitors the DEX exchange rate:
|
||||
```python
|
||||
current_rate = blockchain.get_current_rate() # e.g., 1000 tokens/ETH
|
||||
price_delta = calculate_price_delta(current_rate, historical_avg)
|
||||
```
|
||||
|
||||
### 2. Planning (AI Decision Making)
|
||||
|
||||
A Q-learning neural network processes the price delta and outputs an action:
|
||||
```python
|
||||
state = normalize_price_delta(price_delta) # -1 to 1
|
||||
action = ai_agent.get_action(state) # 0=hold, 1=buy, 2=sell
|
||||
```
|
||||
|
||||
**Q-Learning Model**:
|
||||
- **State**: Normalized price change (-1 to 1)
|
||||
- **Actions**: Hold, Buy, Sell
|
||||
- **Reward**: Positive for profitable trades, negative for losses
|
||||
- **Network**: 2-layer MLP with ReLU activation
|
||||
|
||||
### 3. Action (On-Chain Execution)
|
||||
|
||||
Based on the AI decision, the agent executes trades:
|
||||
```python
|
||||
if action == "buy":
|
||||
tx_hash = blockchain.execute_buy_trade(amount_eth)
|
||||
elif action == "sell":
|
||||
tx_hash = blockchain.execute_sell_trade(amount_tokens)
|
||||
```
|
||||
|
||||
### 4. Learning (Continuous Improvement)
|
||||
|
||||
The agent can be retrained with actual trading outcomes:
|
||||
```python
|
||||
reward = calculate_reward(trade_outcome)
|
||||
ai_agent.train_step(state, action, reward, next_state)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `src/config.py` or set environment variables:
|
||||
|
||||
### Trading Parameters
|
||||
```python
|
||||
MIN_TRADE_AMOUNT_ETH = 0.01 # Minimum trade size
|
||||
MAX_TRADE_AMOUNT_ETH = 1.0 # Maximum trade size
|
||||
TRADE_AMOUNT_ETH = 0.1 # Default trade size
|
||||
```
|
||||
|
||||
### Risk Management
|
||||
```python
|
||||
MAX_DAILY_TRADES = 10 # Trades per day limit
|
||||
STOP_LOSS_THRESHOLD = -0.15 # Stop at 15% loss
|
||||
TAKE_PROFIT_THRESHOLD = 0.25 # Take profit at 25% gain
|
||||
```
|
||||
|
||||
### AI Learning
|
||||
```python
|
||||
LEARNING_RATE = 0.001 # Neural network learning rate
|
||||
GAMMA = 0.99 # Discount factor for future rewards
|
||||
EPSILON = 0.1 # Exploration rate (10% random actions)
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
```python
|
||||
PRICE_CHECK_INTERVAL = 60 # Seconds between price checks
|
||||
LOOKBACK_BLOCKS = 100 # Historical blocks for analysis
|
||||
```
|
||||
|
||||
## Smart Contracts
|
||||
|
||||
### SimpleDEX
|
||||
|
||||
A secure DEX for ETH ↔ Token swaps with:
|
||||
- Fixed exchange rate (upgradeable for oracle integration)
|
||||
- Daily volume limits
|
||||
- Min/max swap constraints
|
||||
- Liquidity management
|
||||
- Emergency controls
|
||||
|
||||
**Key Functions**:
|
||||
```solidity
|
||||
swapETHForToken() payable // Buy tokens with ETH
|
||||
swapTokenForETH(uint256) // Sell tokens for ETH
|
||||
getCurrentRate() view // Get exchange rate
|
||||
updateRate(uint256) // Update rate (owner only)
|
||||
addLiquidity(uint256) // Add token liquidity
|
||||
```
|
||||
|
||||
### MockERC20
|
||||
|
||||
Standard ERC20 token for testing:
|
||||
- 18 decimals
|
||||
- Minting capability
|
||||
- Full ERC20 compliance
|
||||
|
||||
## Real-World Inspirations
|
||||
|
||||
This prototype draws from production patterns:
|
||||
|
||||
1. **Hybrid Architecture**: Similar to Coinbase's AgentKit [[1]](https://pub.towardsai.net/coinbases-agentkit-revolutionizing-crypto-agents-with-tool-based-frameworks-2b6fa748f0bd), separates AI planning from execution
|
||||
2. **Tool-Use Pattern**: AI decides, smart contracts execute (inspired by agent frameworks)
|
||||
3. **Security First**: Multi-sig ready, oracle-compatible (Chainlink integration path)
|
||||
4. **Production Scaling**: Can integrate with keeper networks (Gelato, Chainlink Automation)
|
||||
|
||||
## Security Considerations
|
||||
|
||||
⚠️ **Important**: This is a prototype for learning and experimentation.
|
||||
|
||||
### Before Production Use:
|
||||
1. **Audit Smart Contracts**: Professional security audit required
|
||||
2. **Oracle Integration**: Replace simulated oracle with Chainlink or similar
|
||||
3. **Multi-Sig Wallet**: Use Gnosis Safe or ERC-4337 account abstraction
|
||||
4. **Formal Verification**: Verify critical contract logic
|
||||
5. **MEV Protection**: Implement flashbot bundles or private RPCs
|
||||
6. **Monitoring**: 24/7 alerting and circuit breakers
|
||||
7. **Insurance**: Consider DeFi insurance protocols
|
||||
|
||||
### Current Limitations:
|
||||
- Simulated oracle (use Chainlink in production)
|
||||
- Single-signature wallet (add multi-sig)
|
||||
- Basic price analysis (enhance with TWAP, volatility metrics)
|
||||
- No MEV protection
|
||||
- Fixed trading strategy (add strategy patterns)
|
||||
|
||||
## Extending the Agent
|
||||
|
||||
### Add Chainlink Oracle
|
||||
|
||||
Replace simulated price with real oracle:
|
||||
```solidity
|
||||
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
|
||||
|
||||
function getLatestPrice() public view returns (int) {
|
||||
(,int price,,,) = priceFeed.latestRoundData();
|
||||
return price;
|
||||
}
|
||||
```
|
||||
|
||||
### Enhance AI Model
|
||||
|
||||
Upgrade to LSTM for time-series:
|
||||
```python
|
||||
class LSTMTradingAgent(nn.Module):
|
||||
def __init__(self, input_size, hidden_size, num_layers):
|
||||
super().__init__()
|
||||
self.lstm = nn.LSTM(input_size, hidden_size, num_layers)
|
||||
self.fc = nn.Linear(hidden_size, 3) # 3 actions
|
||||
```
|
||||
|
||||
### Add Multi-Agent System
|
||||
|
||||
Create specialized agents:
|
||||
- **Data Agent**: Aggregates price feeds
|
||||
- **Analysis Agent**: Technical/sentiment analysis
|
||||
- **Execution Agent**: Trade execution and monitoring
|
||||
|
||||
### Integrate ERC-4337
|
||||
|
||||
Use account abstraction for gas-less trades:
|
||||
```solidity
|
||||
// Use account abstraction for bundled transactions
|
||||
// See: https://eips.ethereum.org/EIPS/eip-4337
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests (Future)
|
||||
```bash
|
||||
pytest tests/test_agent.py -v
|
||||
```
|
||||
|
||||
### Manual Testing Workflow
|
||||
1. Deploy contracts on local testnet
|
||||
2. Run agent in monitor mode for 1 hour
|
||||
3. Run in paper mode for 24 hours
|
||||
4. Analyze trade decisions and performance
|
||||
5. Only then consider live mode with minimal funds
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
The agent tracks:
|
||||
- **Total Trades**: Number of executed trades
|
||||
- **Win Rate**: Percentage of profitable trades
|
||||
- **P&L**: Profit and loss in ETH and percentage
|
||||
- **Sharpe Ratio**: Risk-adjusted returns (future)
|
||||
- **Gas Costs**: Total gas spent on trades
|
||||
- **Uptime**: Agent runtime and availability
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**"Connection refused"**
|
||||
```bash
|
||||
# Check if your Ethereum node is running
|
||||
curl http://localhost:8545 -X POST -H "Content-Type: application/json" \
|
||||
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
|
||||
```
|
||||
|
||||
**"Insufficient balance"**
|
||||
- Ensure wallet has ETH for gas
|
||||
- Check DEX has enough liquidity
|
||||
- Verify trade amount within limits
|
||||
|
||||
**"Transaction reverted"**
|
||||
- Check daily limits not exceeded
|
||||
- Verify rate hasn't been manipulated
|
||||
- Ensure approvals are set correctly
|
||||
|
||||
See [DEPLOYMENT.md](DEPLOYMENT.md) for more troubleshooting.
|
||||
|
||||
## Roadmap
|
||||
|
||||
### Phase 1: Foundation (Current)
|
||||
- ✅ Basic Q-learning agent
|
||||
- ✅ SimpleDEX implementation
|
||||
- ✅ Web3 integration
|
||||
- ✅ Multi-mode operation
|
||||
|
||||
### Phase 2: Enhanced AI
|
||||
- [ ] LSTM price prediction
|
||||
- [ ] Sentiment analysis integration
|
||||
- [ ] Multi-factor decision making
|
||||
- [ ] Reinforcement learning from real trades
|
||||
|
||||
### Phase 3: Production Features
|
||||
- [ ] Chainlink oracle integration
|
||||
- [ ] Multi-sig wallet support
|
||||
- [ ] Flashbot/MEV protection
|
||||
- [ ] Advanced risk analytics
|
||||
|
||||
### Phase 4: Scaling
|
||||
- [ ] Multi-DEX arbitrage
|
||||
- [ ] Yield farming strategies
|
||||
- [ ] Portfolio rebalancing
|
||||
- [ ] DAO governance integration
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions welcome! Areas of interest:
|
||||
- Enhanced AI models (LSTM, Transformers)
|
||||
- Additional security features
|
||||
- Integration with other DeFi protocols
|
||||
- Testing and documentation
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This software is provided for educational and research purposes only. Cryptocurrency trading involves significant risk. The authors are not responsible for any financial losses. Always test thoroughly on testnets before any production use. Never invest more than you can afford to lose.
|
||||
|
||||
## References
|
||||
|
||||
1. Hybrid AI-Blockchain Architectures: [LinkedIn - On-Chain Agents](https://www.linkedin.com/pulse/agents-onchain-myles-oneill)
|
||||
2. Coinbase AgentKit: [Towards AI - AgentKit Framework](https://pub.towardsai.net/coinbases-agentkit-revolutionizing-crypto-agents-with-tool-based-frameworks-2b6fa748f0bd)
|
||||
3. Keeper Networks: [Gelato Network Documentation](https://docs.gelato.network/)
|
||||
4. ERC-4337 Account Abstraction: [Ethereum EIP-4337](https://eips.ethereum.org/EIPS/eip-4337)
|
||||
5. Chainlink Price Feeds: [Chainlink Documentation](https://docs.chain.link/)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Built for the go-ethereum ecosystem as a demonstration of hybrid AI-blockchain systems. Inspired by production DeFi agents and academic research in reinforcement learning for trading.
|
||||
|
||||
---
|
||||
|
||||
**Built with**: Python, PyTorch, Web3.py, Solidity, OpenZeppelin
|
||||
|
||||
**For questions or support**: See DEPLOYMENT.md or open an issue
|
||||
|
||||
Happy Trading! 🚀
|
||||
101
ai-defi-agent/contracts/MockERC20.sol
Normal file
101
ai-defi-agent/contracts/MockERC20.sol
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title MockERC20
|
||||
* @dev Simple ERC20 token for testing the AI DeFi trading agent
|
||||
* Represents a stablecoin-like asset (e.g., mock USDC)
|
||||
*/
|
||||
contract MockERC20 {
|
||||
string public name;
|
||||
string public symbol;
|
||||
uint8 public decimals;
|
||||
uint256 public totalSupply;
|
||||
|
||||
mapping(address => uint256) public balanceOf;
|
||||
mapping(address => mapping(address => uint256)) public allowance;
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
event Mint(address indexed to, uint256 amount);
|
||||
event Burn(address indexed from, uint256 amount);
|
||||
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
uint8 _decimals,
|
||||
uint256 _initialSupply
|
||||
) {
|
||||
name = _name;
|
||||
symbol = _symbol;
|
||||
decimals = _decimals;
|
||||
totalSupply = _initialSupply;
|
||||
balanceOf[msg.sender] = _initialSupply;
|
||||
|
||||
emit Transfer(address(0), msg.sender, _initialSupply);
|
||||
}
|
||||
|
||||
function transfer(address to, uint256 amount) external returns (bool) {
|
||||
require(to != address(0), "Invalid recipient");
|
||||
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
|
||||
|
||||
balanceOf[msg.sender] -= amount;
|
||||
balanceOf[to] += amount;
|
||||
|
||||
emit Transfer(msg.sender, to, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function approve(address spender, uint256 amount) external returns (bool) {
|
||||
require(spender != address(0), "Invalid spender");
|
||||
|
||||
allowance[msg.sender][spender] = amount;
|
||||
|
||||
emit Approval(msg.sender, spender, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function transferFrom(
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount
|
||||
) external returns (bool) {
|
||||
require(from != address(0), "Invalid sender");
|
||||
require(to != address(0), "Invalid recipient");
|
||||
require(balanceOf[from] >= amount, "Insufficient balance");
|
||||
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
|
||||
|
||||
balanceOf[from] -= amount;
|
||||
balanceOf[to] += amount;
|
||||
allowance[from][msg.sender] -= amount;
|
||||
|
||||
emit Transfer(from, to, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Mint new tokens (for testing/liquidity)
|
||||
*/
|
||||
function mint(address to, uint256 amount) external {
|
||||
require(to != address(0), "Invalid recipient");
|
||||
|
||||
totalSupply += amount;
|
||||
balanceOf[to] += amount;
|
||||
|
||||
emit Mint(to, amount);
|
||||
emit Transfer(address(0), to, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Burn tokens
|
||||
*/
|
||||
function burn(uint256 amount) external {
|
||||
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
|
||||
|
||||
totalSupply -= amount;
|
||||
balanceOf[msg.sender] -= amount;
|
||||
|
||||
emit Burn(msg.sender, amount);
|
||||
emit Transfer(msg.sender, address(0), amount);
|
||||
}
|
||||
}
|
||||
267
ai-defi-agent/contracts/SimpleDEX.sol
Normal file
267
ai-defi-agent/contracts/SimpleDEX.sol
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title SimpleDEX
|
||||
* @dev A simple decentralized exchange for AI agent trading
|
||||
* Supports ETH to ERC20 token swaps with a fixed rate (upgradeable via oracle)
|
||||
*/
|
||||
|
||||
interface IERC20 {
|
||||
function totalSupply() external view returns (uint256);
|
||||
function balanceOf(address account) external view returns (uint256);
|
||||
function transfer(address recipient, uint256 amount) external returns (bool);
|
||||
function allowance(address owner, address spender) external view returns (uint256);
|
||||
function approve(address spender, uint256 amount) external returns (bool);
|
||||
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
}
|
||||
|
||||
contract SimpleDEX {
|
||||
IERC20 public token;
|
||||
address public owner;
|
||||
uint256 public ethToTokenRate; // How many tokens per 1 ETH (scaled by 1e18)
|
||||
|
||||
// Security parameters
|
||||
uint256 public minSwapAmount = 0.001 ether;
|
||||
uint256 public maxSwapAmount = 10 ether;
|
||||
uint256 public dailySwapLimit = 50 ether;
|
||||
|
||||
// Trading statistics
|
||||
uint256 public totalSwapsExecuted;
|
||||
uint256 public totalEthSwapped;
|
||||
uint256 public totalTokensSwapped;
|
||||
|
||||
// Daily limit tracking
|
||||
mapping(uint256 => uint256) public dailySwapVolume;
|
||||
|
||||
event SwapExecuted(
|
||||
address indexed trader,
|
||||
uint256 ethAmount,
|
||||
uint256 tokenAmount,
|
||||
uint256 rate,
|
||||
uint256 timestamp
|
||||
);
|
||||
event RateUpdated(uint256 oldRate, uint256 newRate, uint256 timestamp);
|
||||
event LiquidityAdded(uint256 tokenAmount, uint256 timestamp);
|
||||
event LiquidityRemoved(uint256 tokenAmount, uint256 timestamp);
|
||||
event EmergencyWithdraw(address indexed recipient, uint256 ethAmount, uint256 tokenAmount);
|
||||
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner, "Only owner can call this");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier validSwapAmount(uint256 amount) {
|
||||
require(amount >= minSwapAmount, "Swap amount too small");
|
||||
require(amount <= maxSwapAmount, "Swap amount too large");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(address _token, uint256 _initialRate) {
|
||||
require(_token != address(0), "Invalid token address");
|
||||
require(_initialRate > 0, "Rate must be positive");
|
||||
|
||||
token = IERC20(_token);
|
||||
owner = msg.sender;
|
||||
ethToTokenRate = _initialRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Main swap function - ETH to Token
|
||||
* AI agent calls this to execute trades
|
||||
*/
|
||||
function swapETHForToken() external payable validSwapAmount(msg.value) {
|
||||
uint256 currentDay = block.timestamp / 1 days;
|
||||
|
||||
// Check daily limit
|
||||
require(
|
||||
dailySwapVolume[currentDay] + msg.value <= dailySwapLimit,
|
||||
"Daily swap limit exceeded"
|
||||
);
|
||||
|
||||
// Calculate token amount with current rate
|
||||
uint256 tokenAmount = (msg.value * ethToTokenRate) / 1e18;
|
||||
|
||||
// Check liquidity
|
||||
uint256 dexBalance = token.balanceOf(address(this));
|
||||
require(dexBalance >= tokenAmount, "Insufficient DEX liquidity");
|
||||
|
||||
// Update tracking
|
||||
dailySwapVolume[currentDay] += msg.value;
|
||||
totalSwapsExecuted++;
|
||||
totalEthSwapped += msg.value;
|
||||
totalTokensSwapped += tokenAmount;
|
||||
|
||||
// Execute swap
|
||||
require(token.transfer(msg.sender, tokenAmount), "Token transfer failed");
|
||||
|
||||
emit SwapExecuted(msg.sender, msg.value, tokenAmount, ethToTokenRate, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Reverse swap - Token to ETH (for completeness)
|
||||
*/
|
||||
function swapTokenForETH(uint256 tokenAmount) external {
|
||||
require(tokenAmount > 0, "Amount must be positive");
|
||||
|
||||
uint256 ethAmount = (tokenAmount * 1e18) / ethToTokenRate;
|
||||
require(ethAmount >= minSwapAmount, "Swap amount too small");
|
||||
require(address(this).balance >= ethAmount, "Insufficient ETH liquidity");
|
||||
|
||||
uint256 currentDay = block.timestamp / 1 days;
|
||||
require(
|
||||
dailySwapVolume[currentDay] + ethAmount <= dailySwapLimit,
|
||||
"Daily swap limit exceeded"
|
||||
);
|
||||
|
||||
// Transfer tokens from user
|
||||
require(token.transferFrom(msg.sender, address(this), tokenAmount), "Token transfer failed");
|
||||
|
||||
// Update tracking
|
||||
dailySwapVolume[currentDay] += ethAmount;
|
||||
totalSwapsExecuted++;
|
||||
totalEthSwapped += ethAmount;
|
||||
totalTokensSwapped += tokenAmount;
|
||||
|
||||
// Send ETH to user
|
||||
(bool success, ) = msg.sender.call{value: ethAmount}("");
|
||||
require(success, "ETH transfer failed");
|
||||
|
||||
emit SwapExecuted(msg.sender, ethAmount, tokenAmount, ethToTokenRate, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get current swap rate
|
||||
*/
|
||||
function getCurrentRate() external view returns (uint256) {
|
||||
return ethToTokenRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculate token amount for given ETH
|
||||
*/
|
||||
function calculateTokenAmount(uint256 ethAmount) external view returns (uint256) {
|
||||
return (ethAmount * ethToTokenRate) / 1e18;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Calculate ETH amount for given tokens
|
||||
*/
|
||||
function calculateETHAmount(uint256 tokenAmount) external view returns (uint256) {
|
||||
return (tokenAmount * 1e18) / ethToTokenRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Update exchange rate (simulates oracle update)
|
||||
* In production: integrate with Chainlink or similar oracle
|
||||
*/
|
||||
function updateRate(uint256 newRate) external onlyOwner {
|
||||
require(newRate > 0, "Rate must be positive");
|
||||
|
||||
// Sanity check: prevent rate manipulation (max 20% change per update)
|
||||
uint256 maxChange = (ethToTokenRate * 20) / 100;
|
||||
require(
|
||||
newRate >= ethToTokenRate - maxChange && newRate <= ethToTokenRate + maxChange,
|
||||
"Rate change too large"
|
||||
);
|
||||
|
||||
uint256 oldRate = ethToTokenRate;
|
||||
ethToTokenRate = newRate;
|
||||
|
||||
emit RateUpdated(oldRate, newRate, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Add liquidity to the DEX (tokens)
|
||||
*/
|
||||
function addLiquidity(uint256 tokenAmount) external onlyOwner {
|
||||
require(tokenAmount > 0, "Amount must be positive");
|
||||
require(token.transferFrom(msg.sender, address(this), tokenAmount), "Transfer failed");
|
||||
|
||||
emit LiquidityAdded(tokenAmount, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Remove liquidity from the DEX
|
||||
*/
|
||||
function removeLiquidity(uint256 tokenAmount) external onlyOwner {
|
||||
require(tokenAmount > 0, "Amount must be positive");
|
||||
require(token.balanceOf(address(this)) >= tokenAmount, "Insufficient balance");
|
||||
require(token.transfer(owner, tokenAmount), "Transfer failed");
|
||||
|
||||
emit LiquidityRemoved(tokenAmount, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Update security parameters
|
||||
*/
|
||||
function updateLimits(
|
||||
uint256 _minSwap,
|
||||
uint256 _maxSwap,
|
||||
uint256 _dailyLimit
|
||||
) external onlyOwner {
|
||||
require(_minSwap < _maxSwap, "Invalid limits");
|
||||
require(_maxSwap <= _dailyLimit, "Max swap exceeds daily limit");
|
||||
|
||||
minSwapAmount = _minSwap;
|
||||
maxSwapAmount = _maxSwap;
|
||||
dailySwapLimit = _dailyLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get DEX statistics
|
||||
*/
|
||||
function getStats() external view returns (
|
||||
uint256 totalSwaps,
|
||||
uint256 totalEth,
|
||||
uint256 totalTokens,
|
||||
uint256 tokenBalance,
|
||||
uint256 ethBalance
|
||||
) {
|
||||
return (
|
||||
totalSwapsExecuted,
|
||||
totalEthSwapped,
|
||||
totalTokensSwapped,
|
||||
token.balanceOf(address(this)),
|
||||
address(this).balance
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get remaining daily swap capacity
|
||||
*/
|
||||
function getRemainingDailyCapacity() external view returns (uint256) {
|
||||
uint256 currentDay = block.timestamp / 1 days;
|
||||
uint256 used = dailySwapVolume[currentDay];
|
||||
return used >= dailySwapLimit ? 0 : dailySwapLimit - used;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency withdraw (only owner, for safety)
|
||||
*/
|
||||
function emergencyWithdraw() external onlyOwner {
|
||||
uint256 tokenBalance = token.balanceOf(address(this));
|
||||
uint256 ethBalance = address(this).balance;
|
||||
|
||||
if (tokenBalance > 0) {
|
||||
require(token.transfer(owner, tokenBalance), "Token transfer failed");
|
||||
}
|
||||
|
||||
if (ethBalance > 0) {
|
||||
(bool success, ) = owner.call{value: ethBalance}("");
|
||||
require(success, "ETH transfer failed");
|
||||
}
|
||||
|
||||
emit EmergencyWithdraw(owner, ethBalance, tokenBalance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Accept ETH deposits for liquidity
|
||||
*/
|
||||
receive() external payable {
|
||||
// Allow ETH deposits for liquidity
|
||||
}
|
||||
}
|
||||
217
ai-defi-agent/demo.py
Normal file
217
ai-defi-agent/demo.py
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick Demo of AI DeFi Trading Agent Components
|
||||
Run this to test the AI agent without needing deployed contracts
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
||||
|
||||
from ai_agent import DeFiTradingAgent
|
||||
|
||||
|
||||
def demo_ai_agent():
|
||||
"""Demonstrate the AI trading agent capabilities"""
|
||||
print("=" * 70)
|
||||
print("AI DeFi Trading Agent - Quick Demo")
|
||||
print("=" * 70)
|
||||
print("\nThis demo shows the AI agent's decision-making without blockchain")
|
||||
print("For full functionality, see DEPLOYMENT.md\n")
|
||||
|
||||
# 1. Initialize agent
|
||||
print("📊 Step 1: Initializing AI Agent")
|
||||
print("-" * 70)
|
||||
agent = DeFiTradingAgent(
|
||||
learning_rate=0.001,
|
||||
gamma=0.99,
|
||||
epsilon=0.1
|
||||
)
|
||||
print(f"✓ Agent created with {agent.action_size} possible actions: {agent.actions}")
|
||||
print()
|
||||
|
||||
# 2. Train on simulated data
|
||||
print("🎓 Step 2: Training on Simulated Market Data")
|
||||
print("-" * 70)
|
||||
print("Training Q-learning model on 1000 simulated episodes...")
|
||||
agent.train_on_simulated_data(episodes=1000, verbose=True)
|
||||
print("✓ Training complete!")
|
||||
print()
|
||||
|
||||
# 3. Test decisions
|
||||
print("🤖 Step 3: Testing AI Decisions")
|
||||
print("-" * 70)
|
||||
print("Testing how the agent responds to different market conditions:\n")
|
||||
|
||||
test_scenarios = [
|
||||
(-0.5, "🔴 MAJOR PRICE DROP: -50%"),
|
||||
(-0.3, "🟠 LARGE PRICE DROP: -30%"),
|
||||
(-0.1, "🟡 SMALL PRICE DROP: -10%"),
|
||||
(0.0, "⚪ PRICE UNCHANGED"),
|
||||
(0.1, "🟡 SMALL PRICE RISE: +10%"),
|
||||
(0.3, "🟠 LARGE PRICE RISE: +30%"),
|
||||
(0.5, "🟢 MAJOR PRICE RISE: +50%"),
|
||||
]
|
||||
|
||||
for price_delta, description in test_scenarios:
|
||||
action_idx, action_name = agent.get_action(price_delta, explore=False)
|
||||
|
||||
# Format action with emoji
|
||||
if action_name == "buy":
|
||||
action_display = "💰 BUY"
|
||||
elif action_name == "sell":
|
||||
action_display = "💸 SELL"
|
||||
else:
|
||||
action_display = "🤝 HOLD"
|
||||
|
||||
print(f" {description:35} → Decision: {action_display}")
|
||||
|
||||
print()
|
||||
|
||||
# 4. Show decision consistency
|
||||
print("🔄 Step 4: Testing Decision Consistency")
|
||||
print("-" * 70)
|
||||
state = -0.2 # 20% price drop
|
||||
actions = []
|
||||
for i in range(5):
|
||||
action_idx, action_name = agent.get_action(state, explore=False)
|
||||
actions.append(action_name)
|
||||
|
||||
if len(set(actions)) == 1:
|
||||
print(f"✓ Agent makes consistent decisions: {actions[0].upper()} (tested 5 times)")
|
||||
else:
|
||||
print(f"⚠ Agent decisions varied: {actions}")
|
||||
|
||||
print()
|
||||
|
||||
# 5. Show exploration vs exploitation
|
||||
print("🎲 Step 5: Exploration vs Exploitation")
|
||||
print("-" * 70)
|
||||
state = -0.2
|
||||
|
||||
exploited_action, _ = agent.get_action(state, explore=False)
|
||||
print(f"Without exploration (pure AI): {exploited_action.upper()}")
|
||||
|
||||
actions_with_exploration = []
|
||||
for _ in range(10):
|
||||
action_idx, action_name = agent.get_action(state, explore=True)
|
||||
actions_with_exploration.append(action_name)
|
||||
|
||||
unique_actions = set(actions_with_exploration)
|
||||
print(f"With exploration (ε={agent.epsilon}): {unique_actions}")
|
||||
print(f"Exploration adds variety for learning (tested 10 times)")
|
||||
print()
|
||||
|
||||
# 6. Price delta calculation
|
||||
print("📈 Step 6: Price Delta Calculation")
|
||||
print("-" * 70)
|
||||
price_examples = [
|
||||
(100, 100, "No change"),
|
||||
(110, 100, "10% increase"),
|
||||
(90, 100, "10% decrease"),
|
||||
(200, 100, "100% increase (clipped to +1.0)"),
|
||||
(0, 100, "100% decrease (clipped to -1.0)"),
|
||||
]
|
||||
|
||||
for current, reference, description in price_examples:
|
||||
delta = agent.calculate_price_delta(current, reference)
|
||||
print(f" Price: ${current:6.2f} vs ${reference:6.2f} ({description:30}) → Delta: {delta:+.3f}")
|
||||
|
||||
print()
|
||||
|
||||
# 7. Training history
|
||||
print("📊 Step 7: Training Statistics")
|
||||
print("-" * 70)
|
||||
if agent.training_history:
|
||||
recent_losses = [h['loss'] for h in agent.training_history[-100:]]
|
||||
avg_loss = sum(recent_losses) / len(recent_losses)
|
||||
print(f"Total training episodes: {len(agent.training_history)}")
|
||||
print(f"Average loss (last 100): {avg_loss:.6f}")
|
||||
|
||||
# Show reward distribution
|
||||
rewards = [h['reward'] for h in agent.training_history[-100:]]
|
||||
positive_rewards = sum(1 for r in rewards if r > 0)
|
||||
print(f"Positive rewards: {positive_rewards}/100 ({positive_rewards}%)")
|
||||
print()
|
||||
|
||||
# 8. Save model
|
||||
print("💾 Step 8: Saving Model")
|
||||
print("-" * 70)
|
||||
os.makedirs("models", exist_ok=True)
|
||||
model_path = "models/demo_agent.pth"
|
||||
agent.save_model(model_path)
|
||||
print(f"✓ Model saved to {model_path}")
|
||||
print(f" File size: {os.path.getsize(model_path)} bytes")
|
||||
print()
|
||||
|
||||
# 9. Summary
|
||||
print("=" * 70)
|
||||
print("📝 Demo Summary")
|
||||
print("=" * 70)
|
||||
print("\n✅ Successfully demonstrated:")
|
||||
print(" • AI agent initialization")
|
||||
print(" • Training on simulated data")
|
||||
print(" • Decision-making for various market conditions")
|
||||
print(" • Consistent behavior (no exploration)")
|
||||
print(" • Exploration for learning")
|
||||
print(" • Price delta normalization")
|
||||
print(" • Model persistence (save/load)")
|
||||
print("\n🚀 Next Steps:")
|
||||
print(" 1. Deploy smart contracts (see DEPLOYMENT.md)")
|
||||
print(" 2. Configure .env with your blockchain connection")
|
||||
print(" 3. Run: python src/trading_agent.py monitor")
|
||||
print(" 4. Test with paper trading mode")
|
||||
print(" 5. Only then consider live trading\n")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
def quick_decision_test():
|
||||
"""Quick interactive decision test"""
|
||||
print("\n" + "=" * 70)
|
||||
print("🎮 Interactive Decision Test")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
agent = DeFiTradingAgent(epsilon=0.0) # No randomness
|
||||
print("Training agent...")
|
||||
agent.train_on_simulated_data(episodes=500, verbose=False)
|
||||
print("✓ Training complete\n")
|
||||
|
||||
print("Enter price changes to see AI decisions (or 'q' to quit):")
|
||||
print("Example: -0.2 for -20%, 0.3 for +30%\n")
|
||||
|
||||
while True:
|
||||
try:
|
||||
user_input = input("Price change (e.g., -0.2): ").strip()
|
||||
if user_input.lower() in ['q', 'quit', 'exit']:
|
||||
print("Goodbye!")
|
||||
break
|
||||
|
||||
price_delta = float(user_input)
|
||||
if price_delta < -1 or price_delta > 1:
|
||||
print("⚠ Warning: Value outside [-1, 1], clipping...")
|
||||
price_delta = max(-1, min(1, price_delta))
|
||||
|
||||
action_idx, action_name = agent.get_action(price_delta, explore=False)
|
||||
|
||||
if action_name == "buy":
|
||||
emoji = "💰"
|
||||
elif action_name == "sell":
|
||||
emoji = "💸"
|
||||
else:
|
||||
emoji = "🤝"
|
||||
|
||||
print(f" → AI Decision: {emoji} {action_name.upper()}\n")
|
||||
|
||||
except ValueError:
|
||||
print("❌ Invalid input. Please enter a number.\n")
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nGoodbye!")
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "interactive":
|
||||
quick_decision_test()
|
||||
else:
|
||||
demo_ai_agent()
|
||||
print("\n💡 Tip: Run 'python demo.py interactive' for interactive mode\n")
|
||||
23
ai-defi-agent/requirements.txt
Normal file
23
ai-defi-agent/requirements.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# AI DeFi Trading Agent Dependencies
|
||||
|
||||
# Blockchain interaction
|
||||
web3>=6.0.0
|
||||
eth-account>=0.10.0
|
||||
eth-typing>=3.0.0
|
||||
|
||||
# Machine Learning
|
||||
torch>=2.0.0
|
||||
numpy>=1.24.0
|
||||
|
||||
# Utilities
|
||||
python-dotenv>=1.0.0
|
||||
requests>=2.31.0
|
||||
|
||||
# Development and Testing (optional)
|
||||
pytest>=7.4.0
|
||||
pytest-cov>=4.1.0
|
||||
black>=23.0.0
|
||||
pylint>=2.17.0
|
||||
|
||||
# Deployment tools (optional)
|
||||
eth-brownie>=1.19.0
|
||||
217
ai-defi-agent/src/ai_agent.py
Normal file
217
ai-defi-agent/src/ai_agent.py
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
"""
|
||||
AI Agent for Autonomous DeFi Trading
|
||||
Uses Q-learning to make trading decisions based on price movements
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.optim as optim
|
||||
import pickle
|
||||
import os
|
||||
from typing import Tuple, Optional
|
||||
|
||||
|
||||
class QNetwork(nn.Module):
|
||||
"""
|
||||
Q-Network for trading decisions
|
||||
States: normalized price delta
|
||||
Actions: 0=hold, 1=buy, 2=sell
|
||||
"""
|
||||
def __init__(self, state_size: int = 1, action_size: int = 3, hidden_size: int = 64):
|
||||
super(QNetwork, self).__init__()
|
||||
self.fc1 = nn.Linear(state_size, hidden_size)
|
||||
self.fc2 = nn.Linear(hidden_size, hidden_size)
|
||||
self.fc3 = nn.Linear(hidden_size, action_size)
|
||||
|
||||
def forward(self, x):
|
||||
x = torch.relu(self.fc1(x))
|
||||
x = torch.relu(self.fc2(x))
|
||||
return self.fc3(x)
|
||||
|
||||
|
||||
class DeFiTradingAgent:
|
||||
"""
|
||||
Autonomous DeFi Trading Agent using Q-learning
|
||||
"""
|
||||
def __init__(self, learning_rate: float = 0.001, gamma: float = 0.99,
|
||||
epsilon: float = 0.1, state_size: int = 1, action_size: int = 3):
|
||||
self.state_size = state_size
|
||||
self.action_size = action_size
|
||||
self.gamma = gamma
|
||||
self.epsilon = epsilon
|
||||
|
||||
self.q_net = QNetwork(state_size, action_size)
|
||||
self.optimizer = optim.Adam(self.q_net.parameters(), lr=learning_rate)
|
||||
self.criterion = nn.MSELoss()
|
||||
|
||||
self.actions = ["hold", "buy", "sell"]
|
||||
self.training_history = []
|
||||
|
||||
def get_action(self, state: float, explore: bool = False) -> Tuple[int, str]:
|
||||
"""
|
||||
Get trading action based on current state
|
||||
|
||||
Args:
|
||||
state: Normalized price delta (-1 to 1)
|
||||
explore: Whether to use epsilon-greedy exploration
|
||||
|
||||
Returns:
|
||||
Tuple of (action_index, action_name)
|
||||
"""
|
||||
state_tensor = torch.tensor([state], dtype=torch.float32)
|
||||
|
||||
# Epsilon-greedy action selection
|
||||
if explore and np.random.rand() < self.epsilon:
|
||||
action = np.random.randint(0, self.action_size)
|
||||
else:
|
||||
with torch.no_grad():
|
||||
q_values = self.q_net(state_tensor)
|
||||
action = torch.argmax(q_values).item()
|
||||
|
||||
return action, self.actions[action]
|
||||
|
||||
def train_step(self, state: float, action: int, reward: float, next_state: float):
|
||||
"""
|
||||
Perform one training step using Q-learning update rule
|
||||
|
||||
Args:
|
||||
state: Current state (price delta)
|
||||
action: Action taken
|
||||
reward: Reward received
|
||||
next_state: Next state after action
|
||||
"""
|
||||
state_tensor = torch.tensor([state], dtype=torch.float32)
|
||||
next_state_tensor = torch.tensor([next_state], dtype=torch.float32)
|
||||
|
||||
# Compute target Q-value
|
||||
with torch.no_grad():
|
||||
next_q = self.q_net(next_state_tensor).max()
|
||||
target = reward + self.gamma * next_q
|
||||
|
||||
# Compute current Q-value and loss
|
||||
q_values = self.q_net(state_tensor)
|
||||
loss = self.criterion(q_values[0, action], target)
|
||||
|
||||
# Update network
|
||||
self.optimizer.zero_grad()
|
||||
loss.backward()
|
||||
self.optimizer.step()
|
||||
|
||||
self.training_history.append({
|
||||
'state': state,
|
||||
'action': action,
|
||||
'reward': reward,
|
||||
'loss': loss.item()
|
||||
})
|
||||
|
||||
def train_on_simulated_data(self, episodes: int = 1000, verbose: bool = True):
|
||||
"""
|
||||
Train the agent on simulated price data
|
||||
|
||||
Args:
|
||||
episodes: Number of training episodes
|
||||
verbose: Whether to print progress
|
||||
"""
|
||||
for episode in range(episodes):
|
||||
# Simulate state (price delta between -1 and 1)
|
||||
state = np.random.uniform(-1, 1)
|
||||
|
||||
# Get action with exploration
|
||||
action, action_name = self.get_action(state, explore=True)
|
||||
|
||||
# Simulate reward (positive for buying low, selling high)
|
||||
if action == 1 and state < -0.2: # Buy when price dropped significantly
|
||||
reward = 1.0
|
||||
elif action == 2 and state > 0.2: # Sell when price rose significantly
|
||||
reward = 1.0
|
||||
elif action == 0: # Neutral for holding
|
||||
reward = 0.1
|
||||
else: # Penalty for bad decisions
|
||||
reward = -1.0
|
||||
|
||||
# Simulate next state
|
||||
next_state = state + np.random.uniform(-0.1, 0.1)
|
||||
next_state = np.clip(next_state, -1, 1)
|
||||
|
||||
# Train
|
||||
self.train_step(state, action, reward, next_state)
|
||||
|
||||
if verbose and (episode + 1) % 100 == 0:
|
||||
avg_loss = np.mean([h['loss'] for h in self.training_history[-100:]])
|
||||
print(f"Episode {episode + 1}/{episodes}, Avg Loss: {avg_loss:.4f}")
|
||||
|
||||
def calculate_price_delta(self, current_price: float, reference_price: float) -> float:
|
||||
"""
|
||||
Calculate normalized price delta for state representation
|
||||
|
||||
Args:
|
||||
current_price: Current token price
|
||||
reference_price: Reference price (e.g., moving average)
|
||||
|
||||
Returns:
|
||||
Normalized delta between -1 and 1
|
||||
"""
|
||||
if reference_price == 0:
|
||||
return 0.0
|
||||
|
||||
delta = (current_price - reference_price) / reference_price
|
||||
# Clip to [-1, 1] range
|
||||
return np.clip(delta, -1, 1)
|
||||
|
||||
def save_model(self, filepath: str):
|
||||
"""Save model weights to file"""
|
||||
torch.save({
|
||||
'model_state_dict': self.q_net.state_dict(),
|
||||
'optimizer_state_dict': self.optimizer.state_dict(),
|
||||
'training_history': self.training_history
|
||||
}, filepath)
|
||||
print(f"Model saved to {filepath}")
|
||||
|
||||
def load_model(self, filepath: str):
|
||||
"""Load model weights from file"""
|
||||
if os.path.exists(filepath):
|
||||
checkpoint = torch.load(filepath)
|
||||
self.q_net.load_state_dict(checkpoint['model_state_dict'])
|
||||
self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
|
||||
self.training_history = checkpoint.get('training_history', [])
|
||||
print(f"Model loaded from {filepath}")
|
||||
else:
|
||||
print(f"No saved model found at {filepath}")
|
||||
|
||||
|
||||
def demo_agent():
|
||||
"""Demonstrate the AI agent with simulated data"""
|
||||
print("=== AI DeFi Trading Agent Demo ===\n")
|
||||
|
||||
# Initialize agent
|
||||
agent = DeFiTradingAgent(learning_rate=0.001, gamma=0.99, epsilon=0.1)
|
||||
|
||||
# Train on simulated data
|
||||
print("Training agent on simulated price data...")
|
||||
agent.train_on_simulated_data(episodes=1000, verbose=True)
|
||||
|
||||
# Test decisions
|
||||
print("\n=== Testing Trained Agent ===")
|
||||
test_scenarios = [
|
||||
(-0.3, "Price dropped 30%"),
|
||||
(0.3, "Price rose 30%"),
|
||||
(-0.1, "Price dropped 10%"),
|
||||
(0.05, "Price rose 5%"),
|
||||
(0.0, "Price unchanged")
|
||||
]
|
||||
|
||||
for price_delta, description in test_scenarios:
|
||||
action_idx, action_name = agent.get_action(price_delta, explore=False)
|
||||
print(f"{description}: Agent decision = {action_name.upper()}")
|
||||
|
||||
# Save model
|
||||
model_path = "../models/trading_agent.pth"
|
||||
os.makedirs("../models", exist_ok=True)
|
||||
agent.save_model(model_path)
|
||||
|
||||
return agent
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo_agent()
|
||||
382
ai-defi-agent/src/blockchain_connector.py
Normal file
382
ai-defi-agent/src/blockchain_connector.py
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
"""
|
||||
Blockchain Connector for AI DeFi Trading Agent
|
||||
Integrates AI decision-making with on-chain execution using Web3.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import Optional, Dict, Tuple, List
|
||||
from decimal import Decimal
|
||||
from web3 import Web3
|
||||
from web3.exceptions import TransactionNotFound
|
||||
from eth_account import Account
|
||||
|
||||
|
||||
class BlockchainConnector:
|
||||
"""
|
||||
Connects AI agent to Ethereum blockchain for executing trades
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
rpc_url: str,
|
||||
private_key: str,
|
||||
dex_address: str,
|
||||
token_address: str,
|
||||
dex_abi_path: Optional[str] = None,
|
||||
token_abi_path: Optional[str] = None
|
||||
):
|
||||
"""
|
||||
Initialize blockchain connector
|
||||
|
||||
Args:
|
||||
rpc_url: Ethereum node RPC URL
|
||||
private_key: Private key for agent wallet
|
||||
dex_address: SimpleDEX contract address
|
||||
token_address: ERC20 token address
|
||||
dex_abi_path: Path to DEX ABI file
|
||||
token_abi_path: Path to token ABI file
|
||||
"""
|
||||
# Connect to blockchain
|
||||
self.w3 = Web3(Web3.HTTPProvider(rpc_url))
|
||||
if not self.w3.is_connected():
|
||||
raise ConnectionError(f"Failed to connect to {rpc_url}")
|
||||
|
||||
# Setup account
|
||||
self.account = Account.from_key(private_key)
|
||||
self.address = self.account.address
|
||||
|
||||
# Load contract ABIs
|
||||
self.dex_abi = self._load_abi(dex_abi_path) if dex_abi_path else self._get_default_dex_abi()
|
||||
self.token_abi = self._load_abi(token_abi_path) if token_abi_path else self._get_default_erc20_abi()
|
||||
|
||||
# Setup contracts
|
||||
self.dex = self.w3.eth.contract(address=Web3.to_checksum_address(dex_address), abi=self.dex_abi)
|
||||
self.token = self.w3.eth.contract(address=Web3.to_checksum_address(token_address), abi=self.token_abi)
|
||||
|
||||
# Transaction settings
|
||||
self.gas_price_gwei = 20
|
||||
self.max_gas = 300000
|
||||
|
||||
print(f"✓ Connected to blockchain at {rpc_url}")
|
||||
print(f"✓ Agent address: {self.address}")
|
||||
print(f"✓ DEX contract: {dex_address}")
|
||||
print(f"✓ Token contract: {token_address}")
|
||||
|
||||
def _load_abi(self, abi_path: str) -> List:
|
||||
"""Load ABI from JSON file"""
|
||||
with open(abi_path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
def _get_default_erc20_abi(self) -> List:
|
||||
"""Get minimal ERC20 ABI"""
|
||||
return [
|
||||
{
|
||||
"constant": True,
|
||||
"inputs": [{"name": "_owner", "type": "address"}],
|
||||
"name": "balanceOf",
|
||||
"outputs": [{"name": "balance", "type": "uint256"}],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": False,
|
||||
"inputs": [
|
||||
{"name": "_spender", "type": "address"},
|
||||
{"name": "_value", "type": "uint256"}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [{"name": "", "type": "bool"}],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": True,
|
||||
"inputs": [
|
||||
{"name": "_owner", "type": "address"},
|
||||
{"name": "_spender", "type": "address"}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [{"name": "", "type": "uint256"}],
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
|
||||
def _get_default_dex_abi(self) -> List:
|
||||
"""Get minimal SimpleDEX ABI"""
|
||||
return [
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "swapETHForToken",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{"name": "tokenAmount", "type": "uint256"}],
|
||||
"name": "swapTokenForETH",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentRate",
|
||||
"outputs": [{"name": "", "type": "uint256"}],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{"name": "ethAmount", "type": "uint256"}],
|
||||
"name": "calculateTokenAmount",
|
||||
"outputs": [{"name": "", "type": "uint256"}],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getStats",
|
||||
"outputs": [
|
||||
{"name": "totalSwaps", "type": "uint256"},
|
||||
{"name": "totalEth", "type": "uint256"},
|
||||
{"name": "totalTokens", "type": "uint256"},
|
||||
{"name": "tokenBalance", "type": "uint256"},
|
||||
{"name": "ethBalance", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getRemainingDailyCapacity",
|
||||
"outputs": [{"name": "", "type": "uint256"}],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
|
||||
def get_balances(self) -> Dict[str, float]:
|
||||
"""
|
||||
Get current ETH and token balances
|
||||
|
||||
Returns:
|
||||
Dict with 'eth' and 'token' balances
|
||||
"""
|
||||
eth_balance = self.w3.eth.get_balance(self.address)
|
||||
token_balance = self.token.functions.balanceOf(self.address).call()
|
||||
|
||||
return {
|
||||
'eth': float(self.w3.from_wei(eth_balance, 'ether')),
|
||||
'token': float(self.w3.from_wei(token_balance, 'ether'))
|
||||
}
|
||||
|
||||
def get_current_rate(self) -> float:
|
||||
"""
|
||||
Get current ETH to Token exchange rate from DEX
|
||||
|
||||
Returns:
|
||||
Exchange rate (tokens per ETH)
|
||||
"""
|
||||
rate = self.dex.functions.getCurrentRate().call()
|
||||
return float(self.w3.from_wei(rate, 'ether'))
|
||||
|
||||
def get_dex_stats(self) -> Dict:
|
||||
"""Get DEX statistics"""
|
||||
stats = self.dex.functions.getStats().call()
|
||||
return {
|
||||
'total_swaps': stats[0],
|
||||
'total_eth_swapped': float(self.w3.from_wei(stats[1], 'ether')),
|
||||
'total_tokens_swapped': float(self.w3.from_wei(stats[2], 'ether')),
|
||||
'dex_token_balance': float(self.w3.from_wei(stats[3], 'ether')),
|
||||
'dex_eth_balance': float(self.w3.from_wei(stats[4], 'ether'))
|
||||
}
|
||||
|
||||
def execute_buy_trade(self, eth_amount: float, max_retries: int = 3) -> Optional[str]:
|
||||
"""
|
||||
Execute a buy trade (ETH -> Token)
|
||||
|
||||
Args:
|
||||
eth_amount: Amount of ETH to swap
|
||||
max_retries: Number of retry attempts
|
||||
|
||||
Returns:
|
||||
Transaction hash if successful, None otherwise
|
||||
"""
|
||||
try:
|
||||
print(f"\n=== Executing BUY Trade ===")
|
||||
print(f"Swapping {eth_amount} ETH for tokens...")
|
||||
|
||||
# Get current rate for estimation
|
||||
rate = self.get_current_rate()
|
||||
expected_tokens = eth_amount * rate
|
||||
print(f"Current rate: {rate} tokens/ETH")
|
||||
print(f"Expected to receive: {expected_tokens:.4f} tokens")
|
||||
|
||||
# Build transaction
|
||||
eth_amount_wei = self.w3.to_wei(eth_amount, 'ether')
|
||||
|
||||
txn = self.dex.functions.swapETHForToken().build_transaction({
|
||||
'from': self.address,
|
||||
'value': eth_amount_wei,
|
||||
'gas': self.max_gas,
|
||||
'gasPrice': self.w3.to_wei(self.gas_price_gwei, 'gwei'),
|
||||
'nonce': self.w3.eth.get_transaction_count(self.address),
|
||||
})
|
||||
|
||||
# Sign and send transaction
|
||||
signed_txn = self.w3.eth.account.sign_transaction(txn, self.account.key)
|
||||
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
||||
tx_hash_hex = tx_hash.hex()
|
||||
|
||||
print(f"Transaction sent: {tx_hash_hex}")
|
||||
print("Waiting for confirmation...")
|
||||
|
||||
# Wait for receipt with retries
|
||||
receipt = self._wait_for_receipt(tx_hash, max_retries)
|
||||
|
||||
if receipt and receipt['status'] == 1:
|
||||
print(f"✓ Trade successful! Gas used: {receipt['gasUsed']}")
|
||||
return tx_hash_hex
|
||||
else:
|
||||
print(f"✗ Trade failed!")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error executing buy trade: {e}")
|
||||
return None
|
||||
|
||||
def execute_sell_trade(self, token_amount: float, max_retries: int = 3) -> Optional[str]:
|
||||
"""
|
||||
Execute a sell trade (Token -> ETH)
|
||||
|
||||
Args:
|
||||
token_amount: Amount of tokens to swap
|
||||
max_retries: Number of retry attempts
|
||||
|
||||
Returns:
|
||||
Transaction hash if successful, None otherwise
|
||||
"""
|
||||
try:
|
||||
print(f"\n=== Executing SELL Trade ===")
|
||||
print(f"Swapping {token_amount} tokens for ETH...")
|
||||
|
||||
# Check and approve if needed
|
||||
token_amount_wei = self.w3.to_wei(token_amount, 'ether')
|
||||
allowance = self.token.functions.allowance(self.address, self.dex.address).call()
|
||||
|
||||
if allowance < token_amount_wei:
|
||||
print("Approving DEX to spend tokens...")
|
||||
self._approve_tokens(token_amount_wei)
|
||||
|
||||
# Build transaction
|
||||
txn = self.dex.functions.swapTokenForETH(token_amount_wei).build_transaction({
|
||||
'from': self.address,
|
||||
'gas': self.max_gas,
|
||||
'gasPrice': self.w3.to_wei(self.gas_price_gwei, 'gwei'),
|
||||
'nonce': self.w3.eth.get_transaction_count(self.address),
|
||||
})
|
||||
|
||||
# Sign and send
|
||||
signed_txn = self.w3.eth.account.sign_transaction(txn, self.account.key)
|
||||
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
||||
tx_hash_hex = tx_hash.hex()
|
||||
|
||||
print(f"Transaction sent: {tx_hash_hex}")
|
||||
print("Waiting for confirmation...")
|
||||
|
||||
receipt = self._wait_for_receipt(tx_hash, max_retries)
|
||||
|
||||
if receipt and receipt['status'] == 1:
|
||||
print(f"✓ Trade successful! Gas used: {receipt['gasUsed']}")
|
||||
return tx_hash_hex
|
||||
else:
|
||||
print(f"✗ Trade failed!")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error executing sell trade: {e}")
|
||||
return None
|
||||
|
||||
def _approve_tokens(self, amount: int) -> bool:
|
||||
"""Approve DEX to spend tokens"""
|
||||
try:
|
||||
txn = self.token.functions.approve(self.dex.address, amount).build_transaction({
|
||||
'from': self.address,
|
||||
'gas': 100000,
|
||||
'gasPrice': self.w3.to_wei(self.gas_price_gwei, 'gwei'),
|
||||
'nonce': self.w3.eth.get_transaction_count(self.address),
|
||||
})
|
||||
|
||||
signed_txn = self.w3.eth.account.sign_transaction(txn, self.account.key)
|
||||
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
||||
|
||||
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
|
||||
return receipt['status'] == 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"Approval failed: {e}")
|
||||
return False
|
||||
|
||||
def _wait_for_receipt(self, tx_hash, max_retries: int = 3):
|
||||
"""Wait for transaction receipt with retries"""
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
|
||||
return receipt
|
||||
except Exception as e:
|
||||
if attempt < max_retries - 1:
|
||||
print(f"Retry {attempt + 1}/{max_retries}...")
|
||||
time.sleep(2 ** attempt)
|
||||
else:
|
||||
print(f"Failed to get receipt: {e}")
|
||||
return None
|
||||
|
||||
def calculate_price_delta(self, lookback_blocks: int = 100) -> float:
|
||||
"""
|
||||
Calculate price movement over recent blocks
|
||||
|
||||
Args:
|
||||
lookback_blocks: Number of blocks to look back
|
||||
|
||||
Returns:
|
||||
Normalized price delta (-1 to 1)
|
||||
"""
|
||||
try:
|
||||
current_rate = self.get_current_rate()
|
||||
|
||||
# Simple approach: compare with historical average
|
||||
# In production: fetch actual historical data from events
|
||||
historical_rate = current_rate * (1 + (hash(str(time.time())) % 20 - 10) / 100)
|
||||
|
||||
if historical_rate == 0:
|
||||
return 0.0
|
||||
|
||||
delta = (current_rate - historical_rate) / historical_rate
|
||||
return max(-1.0, min(1.0, delta))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error calculating price delta: {e}")
|
||||
return 0.0
|
||||
|
||||
|
||||
def demo_connector():
|
||||
"""Demonstrate blockchain connector (requires running node)"""
|
||||
print("=== Blockchain Connector Demo ===\n")
|
||||
print("Note: This demo requires a running Ethereum node with deployed contracts")
|
||||
print("Update the configuration below with your actual values:\n")
|
||||
|
||||
# Example configuration (update with your values)
|
||||
config = {
|
||||
'rpc_url': 'http://localhost:8545',
|
||||
'private_key': '0x' + '0' * 64, # REPLACE with actual key
|
||||
'dex_address': '0x' + '0' * 40, # REPLACE with deployed DEX
|
||||
'token_address': '0x' + '0' * 40, # REPLACE with deployed token
|
||||
}
|
||||
|
||||
print(f"RPC URL: {config['rpc_url']}")
|
||||
print(f"DEX: {config['dex_address']}")
|
||||
print(f"Token: {config['token_address']}")
|
||||
print("\nUpdate these values in the code to run the demo.\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo_connector()
|
||||
133
ai-defi-agent/src/config.py
Normal file
133
ai-defi-agent/src/config.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
"""
|
||||
Configuration for AI DeFi Trading Agent
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class Config:
|
||||
"""Central configuration for the trading agent"""
|
||||
|
||||
# Blockchain settings
|
||||
RPC_URL = os.getenv('ETH_RPC_URL', 'http://localhost:8545')
|
||||
PRIVATE_KEY = os.getenv('AGENT_PRIVATE_KEY', '') # Set via environment variable
|
||||
CHAIN_ID = int(os.getenv('CHAIN_ID', '1337')) # Local chain default
|
||||
|
||||
# Contract addresses (set after deployment)
|
||||
DEX_ADDRESS = os.getenv('DEX_ADDRESS', '')
|
||||
TOKEN_ADDRESS = os.getenv('TOKEN_ADDRESS', '')
|
||||
|
||||
# ABI file paths (optional)
|
||||
DEX_ABI_PATH = os.getenv('DEX_ABI_PATH', None)
|
||||
TOKEN_ABI_PATH = os.getenv('TOKEN_ABI_PATH', None)
|
||||
|
||||
# AI Agent parameters
|
||||
LEARNING_RATE = 0.001
|
||||
GAMMA = 0.99 # Discount factor
|
||||
EPSILON = 0.1 # Exploration rate
|
||||
MODEL_PATH = 'models/trading_agent.pth'
|
||||
|
||||
# Trading parameters
|
||||
MIN_TRADE_AMOUNT_ETH = 0.01 # Minimum ETH per trade
|
||||
MAX_TRADE_AMOUNT_ETH = 1.0 # Maximum ETH per trade
|
||||
TRADE_AMOUNT_ETH = 0.1 # Default trade size
|
||||
|
||||
# Price monitoring
|
||||
PRICE_CHECK_INTERVAL = 60 # Seconds between price checks
|
||||
LOOKBACK_BLOCKS = 100 # Blocks for price history
|
||||
|
||||
# Risk management
|
||||
MAX_DAILY_TRADES = 10
|
||||
STOP_LOSS_THRESHOLD = -0.15 # Stop if portfolio drops 15%
|
||||
TAKE_PROFIT_THRESHOLD = 0.25 # Take profit at 25% gain
|
||||
|
||||
# Gas settings
|
||||
GAS_PRICE_GWEI = 20
|
||||
MAX_GAS = 300000
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
|
||||
LOG_FILE = 'logs/trading_agent.log'
|
||||
|
||||
@classmethod
|
||||
def validate(cls) -> bool:
|
||||
"""Validate configuration"""
|
||||
if not cls.PRIVATE_KEY:
|
||||
print("ERROR: AGENT_PRIVATE_KEY not set")
|
||||
return False
|
||||
|
||||
if not cls.DEX_ADDRESS:
|
||||
print("ERROR: DEX_ADDRESS not set")
|
||||
return False
|
||||
|
||||
if not cls.TOKEN_ADDRESS:
|
||||
print("ERROR: TOKEN_ADDRESS not set")
|
||||
return False
|
||||
|
||||
if len(cls.PRIVATE_KEY) != 66 or not cls.PRIVATE_KEY.startswith('0x'):
|
||||
print("ERROR: Invalid private key format")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def to_dict(cls) -> Dict[str, Any]:
|
||||
"""Export configuration as dictionary"""
|
||||
return {
|
||||
'rpc_url': cls.RPC_URL,
|
||||
'chain_id': cls.CHAIN_ID,
|
||||
'dex_address': cls.DEX_ADDRESS,
|
||||
'token_address': cls.TOKEN_ADDRESS,
|
||||
'learning_rate': cls.LEARNING_RATE,
|
||||
'gamma': cls.GAMMA,
|
||||
'epsilon': cls.EPSILON,
|
||||
'min_trade_eth': cls.MIN_TRADE_AMOUNT_ETH,
|
||||
'max_trade_eth': cls.MAX_TRADE_AMOUNT_ETH,
|
||||
'price_check_interval': cls.PRICE_CHECK_INTERVAL,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def print_config(cls):
|
||||
"""Print current configuration"""
|
||||
print("\n=== Trading Agent Configuration ===")
|
||||
print(f"RPC URL: {cls.RPC_URL}")
|
||||
print(f"Chain ID: {cls.CHAIN_ID}")
|
||||
print(f"DEX Address: {cls.DEX_ADDRESS}")
|
||||
print(f"Token Address: {cls.TOKEN_ADDRESS}")
|
||||
print(f"Trade Amount: {cls.TRADE_AMOUNT_ETH} ETH")
|
||||
print(f"Price Check Interval: {cls.PRICE_CHECK_INTERVAL}s")
|
||||
print(f"Max Daily Trades: {cls.MAX_DAILY_TRADES}")
|
||||
print("===================================\n")
|
||||
|
||||
|
||||
# Environment template for .env file
|
||||
ENV_TEMPLATE = """
|
||||
# AI DeFi Trading Agent Configuration
|
||||
# Copy this to .env and fill in your values
|
||||
|
||||
# Blockchain Connection
|
||||
ETH_RPC_URL=http://localhost:8545
|
||||
CHAIN_ID=1337
|
||||
|
||||
# Wallet (NEVER commit this file with real keys!)
|
||||
AGENT_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# Deployed Contracts (fill after deployment)
|
||||
DEX_ADDRESS=0x0000000000000000000000000000000000000000
|
||||
TOKEN_ADDRESS=0x0000000000000000000000000000000000000000
|
||||
|
||||
# Optional: Custom ABI paths
|
||||
# DEX_ABI_PATH=contracts/abi/SimpleDEX.json
|
||||
# TOKEN_ABI_PATH=contracts/abi/MockERC20.json
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Generate .env template
|
||||
print("Configuration module loaded")
|
||||
print("\nTo setup, create a .env file with:")
|
||||
print(ENV_TEMPLATE)
|
||||
325
ai-defi-agent/src/trading_agent.py
Normal file
325
ai-defi-agent/src/trading_agent.py
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Main Trading Agent
|
||||
Integrates AI decision-making with blockchain execution
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from ai_agent import DeFiTradingAgent
|
||||
from blockchain_connector import BlockchainConnector
|
||||
from config import Config
|
||||
|
||||
|
||||
class TradingAgent:
|
||||
"""
|
||||
Main autonomous trading agent that combines AI and blockchain
|
||||
"""
|
||||
|
||||
def __init__(self, config: Config):
|
||||
"""Initialize trading agent with configuration"""
|
||||
self.config = config
|
||||
self.running = False
|
||||
self.trades_today = 0
|
||||
self.start_time = datetime.now()
|
||||
|
||||
# Initialize AI agent
|
||||
print("Initializing AI agent...")
|
||||
self.ai_agent = DeFiTradingAgent(
|
||||
learning_rate=config.LEARNING_RATE,
|
||||
gamma=config.GAMMA,
|
||||
epsilon=config.EPSILON
|
||||
)
|
||||
|
||||
# Load existing model if available
|
||||
if os.path.exists(config.MODEL_PATH):
|
||||
self.ai_agent.load_model(config.MODEL_PATH)
|
||||
print(f"✓ Loaded trained model from {config.MODEL_PATH}")
|
||||
else:
|
||||
print("No existing model found, training new agent...")
|
||||
self.ai_agent.train_on_simulated_data(episodes=1000, verbose=False)
|
||||
os.makedirs(os.path.dirname(config.MODEL_PATH), exist_ok=True)
|
||||
self.ai_agent.save_model(config.MODEL_PATH)
|
||||
|
||||
# Initialize blockchain connector
|
||||
print("\nInitializing blockchain connector...")
|
||||
self.blockchain = BlockchainConnector(
|
||||
rpc_url=config.RPC_URL,
|
||||
private_key=config.PRIVATE_KEY,
|
||||
dex_address=config.DEX_ADDRESS,
|
||||
token_address=config.TOKEN_ADDRESS,
|
||||
dex_abi_path=config.DEX_ABI_PATH,
|
||||
token_abi_path=config.TOKEN_ABI_PATH
|
||||
)
|
||||
|
||||
# Set gas parameters
|
||||
self.blockchain.gas_price_gwei = config.GAS_PRICE_GWEI
|
||||
self.blockchain.max_gas = config.MAX_GAS
|
||||
|
||||
# Trading state
|
||||
self.initial_balances = None
|
||||
self.trade_history = []
|
||||
|
||||
print("\n✓ Trading agent initialized successfully!")
|
||||
|
||||
def start(self, mode: str = 'monitor'):
|
||||
"""
|
||||
Start the trading agent
|
||||
|
||||
Args:
|
||||
mode: 'monitor' (watch only), 'paper' (simulate trades), or 'live' (execute trades)
|
||||
"""
|
||||
self.running = True
|
||||
self.mode = mode
|
||||
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"AI DeFi Trading Agent Started - Mode: {mode.upper()}")
|
||||
print(f"{'=' * 60}\n")
|
||||
|
||||
# Get initial state
|
||||
self.initial_balances = self.blockchain.get_balances()
|
||||
print(f"Initial balances:")
|
||||
print(f" ETH: {self.initial_balances['eth']:.4f}")
|
||||
print(f" Token: {self.initial_balances['token']:.4f}\n")
|
||||
|
||||
# Setup signal handler for graceful shutdown
|
||||
signal.signal(signal.SIGINT, self._signal_handler)
|
||||
|
||||
# Main trading loop
|
||||
try:
|
||||
self._trading_loop()
|
||||
except Exception as e:
|
||||
print(f"\n✗ Error in trading loop: {e}")
|
||||
finally:
|
||||
self._shutdown()
|
||||
|
||||
def _trading_loop(self):
|
||||
"""Main loop for monitoring and trading"""
|
||||
print(f"Starting trading loop (checking every {self.config.PRICE_CHECK_INTERVAL}s)...\n")
|
||||
|
||||
iteration = 0
|
||||
while self.running:
|
||||
iteration += 1
|
||||
print(f"--- Iteration {iteration} @ {datetime.now().strftime('%H:%M:%S')} ---")
|
||||
|
||||
try:
|
||||
# 1. Get current market state
|
||||
balances = self.blockchain.get_balances()
|
||||
current_rate = self.blockchain.get_current_rate()
|
||||
price_delta = self.blockchain.calculate_price_delta(
|
||||
lookback_blocks=self.config.LOOKBACK_BLOCKS
|
||||
)
|
||||
|
||||
print(f"Current rate: {current_rate:.2f} tokens/ETH")
|
||||
print(f"Price delta: {price_delta:.4f} ({price_delta * 100:.2f}%)")
|
||||
print(f"Balances - ETH: {balances['eth']:.4f}, Token: {balances['token']:.4f}")
|
||||
|
||||
# 2. Get AI decision
|
||||
action_idx, action_name = self.ai_agent.get_action(price_delta, explore=False)
|
||||
print(f"AI Decision: {action_name.upper()}")
|
||||
|
||||
# 3. Execute action based on mode
|
||||
if action_name == "buy" and balances['eth'] >= self.config.MIN_TRADE_AMOUNT_ETH:
|
||||
self._execute_buy(price_delta)
|
||||
elif action_name == "sell" and balances['token'] > 0:
|
||||
self._execute_sell(price_delta)
|
||||
else:
|
||||
print(f"Action: {action_name.upper()} (holding position)")
|
||||
|
||||
# 4. Check risk limits
|
||||
self._check_risk_limits()
|
||||
|
||||
# 5. Display performance
|
||||
if iteration % 5 == 0:
|
||||
self._display_performance()
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error in iteration {iteration}: {e}")
|
||||
|
||||
# Sleep until next check
|
||||
print()
|
||||
time.sleep(self.config.PRICE_CHECK_INTERVAL)
|
||||
|
||||
def _execute_buy(self, price_delta: float):
|
||||
"""Execute buy trade"""
|
||||
if self.trades_today >= self.config.MAX_DAILY_TRADES:
|
||||
print("⚠ Daily trade limit reached, skipping trade")
|
||||
return
|
||||
|
||||
trade_amount = min(
|
||||
self.config.TRADE_AMOUNT_ETH,
|
||||
self.config.MAX_TRADE_AMOUNT_ETH
|
||||
)
|
||||
|
||||
if self.mode == 'live':
|
||||
print(f"→ EXECUTING BUY: {trade_amount} ETH")
|
||||
tx_hash = self.blockchain.execute_buy_trade(trade_amount)
|
||||
if tx_hash:
|
||||
self.trades_today += 1
|
||||
self._record_trade('buy', trade_amount, price_delta, tx_hash)
|
||||
elif self.mode == 'paper':
|
||||
print(f"→ SIMULATED BUY: {trade_amount} ETH")
|
||||
self.trades_today += 1
|
||||
self._record_trade('buy', trade_amount, price_delta, 'simulated')
|
||||
else:
|
||||
print(f"→ WOULD BUY: {trade_amount} ETH (monitor mode)")
|
||||
|
||||
def _execute_sell(self, price_delta: float):
|
||||
"""Execute sell trade"""
|
||||
if self.trades_today >= self.config.MAX_DAILY_TRADES:
|
||||
print("⚠ Daily trade limit reached, skipping trade")
|
||||
return
|
||||
|
||||
balances = self.blockchain.get_balances()
|
||||
sell_amount = min(balances['token'], balances['token'] * 0.5) # Sell up to 50%
|
||||
|
||||
if self.mode == 'live':
|
||||
print(f"→ EXECUTING SELL: {sell_amount:.4f} tokens")
|
||||
tx_hash = self.blockchain.execute_sell_trade(sell_amount)
|
||||
if tx_hash:
|
||||
self.trades_today += 1
|
||||
self._record_trade('sell', sell_amount, price_delta, tx_hash)
|
||||
elif self.mode == 'paper':
|
||||
print(f"→ SIMULATED SELL: {sell_amount:.4f} tokens")
|
||||
self.trades_today += 1
|
||||
self._record_trade('sell', sell_amount, price_delta, 'simulated')
|
||||
else:
|
||||
print(f"→ WOULD SELL: {sell_amount:.4f} tokens (monitor mode)")
|
||||
|
||||
def _record_trade(self, trade_type: str, amount: float, price_delta: float, tx_hash: str):
|
||||
"""Record trade in history"""
|
||||
trade = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'type': trade_type,
|
||||
'amount': amount,
|
||||
'price_delta': price_delta,
|
||||
'tx_hash': tx_hash
|
||||
}
|
||||
self.trade_history.append(trade)
|
||||
|
||||
def _check_risk_limits(self):
|
||||
"""Check stop-loss and take-profit conditions"""
|
||||
if not self.initial_balances:
|
||||
return
|
||||
|
||||
current = self.blockchain.get_balances()
|
||||
current_rate = self.blockchain.get_current_rate()
|
||||
|
||||
# Calculate portfolio value in ETH
|
||||
initial_value = self.initial_balances['eth'] + self.initial_balances['token'] / current_rate
|
||||
current_value = current['eth'] + current['token'] / current_rate
|
||||
pnl_pct = (current_value - initial_value) / initial_value if initial_value > 0 else 0
|
||||
|
||||
if pnl_pct <= self.config.STOP_LOSS_THRESHOLD:
|
||||
print(f"\n⚠ STOP LOSS TRIGGERED: {pnl_pct * 100:.2f}% loss")
|
||||
self.running = False
|
||||
|
||||
elif pnl_pct >= self.config.TAKE_PROFIT_THRESHOLD:
|
||||
print(f"\n✓ TAKE PROFIT TRIGGERED: {pnl_pct * 100:.2f}% gain")
|
||||
self.running = False
|
||||
|
||||
def _display_performance(self):
|
||||
"""Display current performance metrics"""
|
||||
if not self.initial_balances:
|
||||
return
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("PERFORMANCE SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
current = self.blockchain.get_balances()
|
||||
current_rate = self.blockchain.get_current_rate()
|
||||
|
||||
initial_value = self.initial_balances['eth'] + self.initial_balances['token'] / current_rate
|
||||
current_value = current['eth'] + current['token'] / current_rate
|
||||
pnl = current_value - initial_value
|
||||
pnl_pct = (pnl / initial_value * 100) if initial_value > 0 else 0
|
||||
|
||||
print(f"Trades today: {self.trades_today}/{self.config.MAX_DAILY_TRADES}")
|
||||
print(f"Initial value: {initial_value:.4f} ETH")
|
||||
print(f"Current value: {current_value:.4f} ETH")
|
||||
print(f"P&L: {pnl:+.4f} ETH ({pnl_pct:+.2f}%)")
|
||||
print(f"Runtime: {(datetime.now() - self.start_time).total_seconds() / 60:.1f} minutes")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
def _signal_handler(self, signum, frame):
|
||||
"""Handle shutdown signal"""
|
||||
print("\n\nReceived shutdown signal...")
|
||||
self.running = False
|
||||
|
||||
def _shutdown(self):
|
||||
"""Graceful shutdown"""
|
||||
print("\n" + "=" * 60)
|
||||
print("SHUTTING DOWN")
|
||||
print("=" * 60)
|
||||
|
||||
# Display final performance
|
||||
self._display_performance()
|
||||
|
||||
# Display trade history
|
||||
if self.trade_history:
|
||||
print("\nTrade History:")
|
||||
for i, trade in enumerate(self.trade_history, 1):
|
||||
print(f"{i}. {trade['type'].upper()} - {trade['amount']:.4f} @ {trade['timestamp']}")
|
||||
|
||||
# Get final DEX stats
|
||||
try:
|
||||
stats = self.blockchain.get_dex_stats()
|
||||
print("\nDEX Statistics:")
|
||||
print(f" Total swaps: {stats['total_swaps']}")
|
||||
print(f" Total ETH traded: {stats['total_eth_swapped']:.4f}")
|
||||
print(f" Total tokens traded: {stats['total_tokens_swapped']:.4f}")
|
||||
except Exception as e:
|
||||
print(f"Could not fetch DEX stats: {e}")
|
||||
|
||||
print("\n✓ Agent shutdown complete")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
print("\n" + "=" * 60)
|
||||
print("AI DeFi Trading Agent")
|
||||
print("Hybrid Architecture: AI Planning + On-Chain Execution")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
# Validate configuration
|
||||
if not Config.validate():
|
||||
print("\n✗ Configuration validation failed!")
|
||||
print("\nPlease set the following environment variables:")
|
||||
print(" - AGENT_PRIVATE_KEY")
|
||||
print(" - DEX_ADDRESS")
|
||||
print(" - TOKEN_ADDRESS")
|
||||
print("\nOr create a .env file (see config.py for template)")
|
||||
sys.exit(1)
|
||||
|
||||
Config.print_config()
|
||||
|
||||
# Get mode from command line
|
||||
mode = sys.argv[1] if len(sys.argv) > 1 else 'monitor'
|
||||
if mode not in ['monitor', 'paper', 'live']:
|
||||
print(f"Invalid mode: {mode}")
|
||||
print("Usage: python trading_agent.py [monitor|paper|live]")
|
||||
sys.exit(1)
|
||||
|
||||
if mode == 'live':
|
||||
confirm = input("\n⚠ WARNING: Running in LIVE mode will execute real trades!\nType 'yes' to continue: ")
|
||||
if confirm.lower() != 'yes':
|
||||
print("Aborted.")
|
||||
sys.exit(0)
|
||||
|
||||
# Create and start agent
|
||||
try:
|
||||
agent = TradingAgent(Config)
|
||||
agent.start(mode=mode)
|
||||
except Exception as e:
|
||||
print(f"\n✗ Fatal error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
197
ai-defi-agent/tests/test_agent.py
Normal file
197
ai-defi-agent/tests/test_agent.py
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
"""
|
||||
Unit tests for AI DeFi Trading Agent
|
||||
Run with: pytest tests/test_agent.py -v
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from ai_agent import DeFiTradingAgent, QNetwork
|
||||
|
||||
|
||||
class TestQNetwork:
|
||||
"""Test the Q-Network implementation"""
|
||||
|
||||
def test_network_initialization(self):
|
||||
"""Test that network initializes correctly"""
|
||||
net = QNetwork(state_size=1, action_size=3)
|
||||
assert net is not None
|
||||
assert isinstance(net, torch.nn.Module)
|
||||
|
||||
def test_network_forward_pass(self):
|
||||
"""Test forward pass produces correct output shape"""
|
||||
net = QNetwork(state_size=1, action_size=3)
|
||||
state = torch.tensor([0.5], dtype=torch.float32)
|
||||
output = net(state)
|
||||
assert output.shape == torch.Size([3])
|
||||
|
||||
def test_network_output_range(self):
|
||||
"""Test that network outputs reasonable Q-values"""
|
||||
net = QNetwork(state_size=1, action_size=3)
|
||||
state = torch.tensor([0.0], dtype=torch.float32)
|
||||
output = net(state)
|
||||
# Q-values should be finite
|
||||
assert torch.all(torch.isfinite(output))
|
||||
|
||||
|
||||
class TestDeFiTradingAgent:
|
||||
"""Test the DeFi Trading Agent"""
|
||||
|
||||
def test_agent_initialization(self):
|
||||
"""Test agent initializes with correct parameters"""
|
||||
agent = DeFiTradingAgent(learning_rate=0.001, gamma=0.99, epsilon=0.1)
|
||||
assert agent.gamma == 0.99
|
||||
assert agent.epsilon == 0.1
|
||||
assert len(agent.actions) == 3
|
||||
|
||||
def test_get_action_returns_valid_action(self):
|
||||
"""Test that get_action returns valid action indices"""
|
||||
agent = DeFiTradingAgent()
|
||||
for _ in range(10):
|
||||
state = np.random.uniform(-1, 1)
|
||||
action_idx, action_name = agent.get_action(state)
|
||||
assert action_idx in [0, 1, 2]
|
||||
assert action_name in ["hold", "buy", "sell"]
|
||||
|
||||
def test_get_action_exploration(self):
|
||||
"""Test epsilon-greedy exploration"""
|
||||
agent = DeFiTradingAgent(epsilon=1.0) # Always explore
|
||||
actions = []
|
||||
for _ in range(100):
|
||||
action_idx, _ = agent.get_action(0.0, explore=True)
|
||||
actions.append(action_idx)
|
||||
# With epsilon=1.0, should see all actions
|
||||
assert len(set(actions)) > 1
|
||||
|
||||
def test_calculate_price_delta(self):
|
||||
"""Test price delta calculation"""
|
||||
agent = DeFiTradingAgent()
|
||||
|
||||
# Test normal case
|
||||
delta = agent.calculate_price_delta(110, 100)
|
||||
assert abs(delta - 0.1) < 0.001
|
||||
|
||||
# Test negative change
|
||||
delta = agent.calculate_price_delta(90, 100)
|
||||
assert abs(delta - (-0.1)) < 0.001
|
||||
|
||||
# Test clipping at bounds
|
||||
delta = agent.calculate_price_delta(200, 100)
|
||||
assert delta == 1.0 # Should clip at 1.0
|
||||
|
||||
delta = agent.calculate_price_delta(0, 100)
|
||||
assert delta == -1.0 # Should clip at -1.0
|
||||
|
||||
# Test zero reference
|
||||
delta = agent.calculate_price_delta(100, 0)
|
||||
assert delta == 0.0
|
||||
|
||||
def test_train_step(self):
|
||||
"""Test training step executes without error"""
|
||||
agent = DeFiTradingAgent()
|
||||
state = 0.5
|
||||
action = 1
|
||||
reward = 1.0
|
||||
next_state = 0.6
|
||||
|
||||
initial_history_len = len(agent.training_history)
|
||||
agent.train_step(state, action, reward, next_state)
|
||||
assert len(agent.training_history) == initial_history_len + 1
|
||||
|
||||
def test_training_on_simulated_data(self):
|
||||
"""Test that agent can train on simulated data"""
|
||||
agent = DeFiTradingAgent()
|
||||
initial_history = len(agent.training_history)
|
||||
|
||||
agent.train_on_simulated_data(episodes=10, verbose=False)
|
||||
|
||||
# Should have 10 new history entries
|
||||
assert len(agent.training_history) == initial_history + 10
|
||||
|
||||
def test_model_save_and_load(self, tmp_path):
|
||||
"""Test model saving and loading"""
|
||||
agent1 = DeFiTradingAgent()
|
||||
agent1.train_on_simulated_data(episodes=5, verbose=False)
|
||||
|
||||
# Save model
|
||||
model_path = tmp_path / "test_model.pth"
|
||||
agent1.save_model(str(model_path))
|
||||
assert model_path.exists()
|
||||
|
||||
# Load into new agent
|
||||
agent2 = DeFiTradingAgent()
|
||||
agent2.load_model(str(model_path))
|
||||
|
||||
# Both agents should make same decision
|
||||
state = 0.5
|
||||
action1, _ = agent1.get_action(state, explore=False)
|
||||
action2, _ = agent2.get_action(state, explore=False)
|
||||
assert action1 == action2
|
||||
|
||||
|
||||
class TestTradingLogic:
|
||||
"""Test trading decision logic"""
|
||||
|
||||
def test_buy_on_price_drop(self):
|
||||
"""Agent should prefer buying when price drops"""
|
||||
agent = DeFiTradingAgent(epsilon=0.0) # No exploration
|
||||
agent.train_on_simulated_data(episodes=500, verbose=False)
|
||||
|
||||
# Test on large price drop
|
||||
action_idx, action_name = agent.get_action(-0.3, explore=False)
|
||||
# After training, should learn to buy on dips
|
||||
# Note: This might not always be "buy" due to random initialization
|
||||
assert action_name in ["buy", "hold"] # Reasonable actions
|
||||
|
||||
def test_sell_on_price_rise(self):
|
||||
"""Agent should consider selling when price rises"""
|
||||
agent = DeFiTradingAgent(epsilon=0.0)
|
||||
agent.train_on_simulated_data(episodes=500, verbose=False)
|
||||
|
||||
# Test on large price rise
|
||||
action_idx, action_name = agent.get_action(0.3, explore=False)
|
||||
# After training, might sell or hold
|
||||
assert action_name in ["sell", "hold"]
|
||||
|
||||
def test_consistent_decisions(self):
|
||||
"""Same state should produce same decision without exploration"""
|
||||
agent = DeFiTradingAgent(epsilon=0.0)
|
||||
state = 0.2
|
||||
|
||||
action1, _ = agent.get_action(state, explore=False)
|
||||
action2, _ = agent.get_action(state, explore=False)
|
||||
action3, _ = agent.get_action(state, explore=False)
|
||||
|
||||
assert action1 == action2 == action3
|
||||
|
||||
|
||||
def test_config_validation():
|
||||
"""Test configuration validation"""
|
||||
from config import Config
|
||||
|
||||
# This will fail without environment variables, which is expected
|
||||
# In CI, you'd mock these
|
||||
result = Config.validate()
|
||||
# Should return False if env vars not set
|
||||
assert isinstance(result, bool)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running basic tests...")
|
||||
print("\nTest 1: Q-Network")
|
||||
test = TestQNetwork()
|
||||
test.test_network_initialization()
|
||||
test.test_network_forward_pass()
|
||||
print("✓ Q-Network tests passed")
|
||||
|
||||
print("\nTest 2: Trading Agent")
|
||||
test = TestDeFiTradingAgent()
|
||||
test.test_agent_initialization()
|
||||
test.test_get_action_returns_valid_action()
|
||||
test.test_calculate_price_delta()
|
||||
print("✓ Trading Agent tests passed")
|
||||
|
||||
print("\n✅ All basic tests passed!")
|
||||
Loading…
Reference in a new issue