TradingAgents/docs/plans/2026-03-11-dex-data-layer.md

19 KiB

DEX Data Layer Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Create DEX data layer for TradingAgents - enabling crypto token analysis instead of traditional stocks. Phase 1 targets CoinGecko provider with core OHLCV and token info tools.

Architecture: Priority-based iterative providers. Phase 1: CoinGecko (market data). Phase 2: DeFiLlama (TVL). Phase 3: Birdeye (whale tracking). Maintains existing LangGraph agent structure.

Tech Stack: Python, LangGraph, CoinGecko API, yfinance (existing), stockstats (technical indicators)


Prerequisites

  • Ensure .env has CoinGecko API key (free tier: 10-30 calls/min)
  • Or set: export COINGECKO_API_KEY=your_key (optional for free endpoints)

Phase 1: CoinGecko Provider

Task 1: Create DEX Provider Directory Structure

Files:

  • Create: tradingagents/dataflows/dex/__init__.py

Step 1: Create the directory and init file

# tradingagents/dataflows/dex/__init__.py
"""DEX Data Providers for TradingAgents."""

from .coingecko_provider import CoinGeckoProvider, get_coin_ohlcv, get_coin_info

__all__ = ["CoinGeckoProvider", "get_coin_ohlcv", "get_coin_info"]

Step 2: Commit

git add tradingagents/dataflows/dex/__init__.py
git commit -m "feat(dex): create DEX dataflows directory structure"

Task 2: Create CoinGecko Provider

Files:

  • Create: tradingagents/dataflows/dex/coingecko_provider.py
  • Modify: tradingagents/dataflows/dex/__init__.py

Step 1: Write the failing test

Run: pytest tradingagents/dataflows/dex/test_coingecko.py -v (will fail - file doesn't exist yet)

# tradingagents/dataflows/dex/test_coingecko.py
import pytest
from tradingagents.dataflows.dex.coingecko_provider import get_coin_ohlcv, get_coin_info

@pytest.mark.asyncio
async def test_get_coin_ohlcv_returns_data():
    """Test that get_coin_ohlcv returns OHLCV data for SOL."""
    result = await get_coin_ohlcv("solana", "usd", 7)
    assert " timestamp " in result.lower() or "open" in result.lower()
    assert len(result) > 100

@pytest.mark.asyncio
async def test_get_coin_info_returns_metadata():
    """Test that get_coin_info returns token metadata."""
    result = await get_coin_info("solana")
    assert "solana" in result.lower()
    assert "market_cap" in result.lower() or "$" in result

Step 2: Run test to verify it fails

Run: pytest tradingagents/dataflows/dex/test_coingecko.py -v Expected: FAIL -ModuleNotFoundError

Step 3: Write the CoinGecko provider implementation

# tradingagents/dataflows/dex/coingecko_provider.py
"""CoinGecko API provider for DEX data."""

import os
from typing import Optional
import httpx
import pandas as pd
from datetime import datetime, timedelta

COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3"

class CoinGeckoProvider:
    """Provider for CoinGecko API calls."""

    def __init__(self, api_key: Optional[str] = None):
        self.api_key = api_key or os.getenv("COINGECKO_API_KEY")
        self.client = httpx.AsyncClient(timeout=30.0)

    async def close(self):
        await self.client.aclose()

    async def _get(self, endpoint: str, params: dict = None) -> dict:
        """Make authenticated GET request to CoinGecko."""
        headers = {}
        if self.api_key:
            headers["x-cg-demo-api-key"] = self.api_key

        params = params or {}
        response = await self.client.get(
            f"{COINGECKO_BASE_URL}{endpoint}",
            headers=headers,
            params=params
        )
        response.raise_for_status()
        return response.json()

    async def get_ohlc(self, coin_id: str, vs_currency: str = "usd", days: int = 7) -> list:
        """Get OHLC data for a coin."""
        return await self._get(
            f"/coins/{coin_id}/ohlc",
            params={"vs_currency": vs_currency, "days": days}
        )

    async def get_coin_data(self, coin_id: str) -> dict:
        """Get detailed coin data including market info."""
        return await self._get(
            f"/coins/{coin_id}",
            params={
                "localization": "false",
                "tickers": "false",
                "market_data": "true",
                "community_data": "false",
                "developer_data": "false",
                "sparkline": "false"
            }
        )


# Global provider instance
_provider: Optional[CoinGeckoProvider] = None

def _get_provider() -> CoinGeckoProvider:
    global _provider
    if _provider is None:
        _provider = CoinGeckoProvider()
    return _provider


async def get_coin_ohlcv(coin_id: str, vs_currency: str = "usd", days: int = 7) -> str:
    """Get OHLCV data for a cryptocurrency token.

    Args:
        coin_id: CoinGecko coin ID (e.g., 'solana', 'bitcoin', 'ethereum')
        vs_currency: Target currency (default: 'usd')
        days: Number of days of data (1-365)

    Returns:
        Formatted OHLCV data string for LLM consumption
    """
    provider = _get_provider()
    try:
        ohlc_data = await provider.get_ohlc(coin_id, vs_currency, days)

        if not ohlc_data:
            return f"No OHLCV data available for {coin_id}"

        # Convert to readable format
        lines = [f"OHLCV Data for {coin_id.upper()} (last {days} days)"]
        lines.append("=" * 60)

        for i, (timestamp, open_val, high, low, close) in enumerate(ohlc_data):
            date = datetime.fromtimestamp(timestamp // 1000)
            date_str = date.strftime("%Y-%m-%d")
            lines.append(
                f"{date_str} | O: {open_val:>10.2f} | H: {high:>10.2f} | "
                f"L: {low:>10.2f} | C: {close:>10.2f}"
            )

        # Calculate summary
        closes = [row[4] for row in ohlc_data]
        if closes:
            price_change = ((closes[-1] - closes[0]) / closes[0]) * 100
            lines.append("")
            lines.append(f"Price Change: {price_change:+.2f}%")
            lines.append(f"High: ${max(closes):.2f} | Low: ${min(closes):.2f}")

        return "\n".join(lines)

    except httpx.HTTPStatusError as e:
        return f"Error fetching OHLCV data: {e.response.status_code}"
    except Exception as e:
        return f"Error fetching OHLCV data: {str(e)}"


async def get_coin_info(coin_id: str) -> str:
    """Get token metadata and market data.

    Args:
        coin_id: CoinGecko coin ID (e.g., 'solana', 'bitcoin')

    Returns:
        Formatted token info string for LLM consumption
    """
    provider = _get_provider()
    try:
        data = await provider.get_coin_data(coin_id)

        if not data:
            return f"No data available for {coin_id}"

        market = data.get("market_data", {})
        lines = [f"Token Information: {data.get('name', coin_id).upper()} ({data.get('symbol', '').upper()})"]
        lines.append("=" * 60)

        # Market data
        current_price = market.get("current_price", {}).get("usd", 0)
        lines.append(f"Current Price: ${current_price:,.2f}")

        market_cap = market.get("market_cap", {}).get("usd", 0)
        lines.append(f"Market Cap: ${market_cap:,.0f}")

        volume = market.get("total_volume", {}).get("usd", 0)
        lines.append(f"24h Volume: ${volume:,.0f}")

        # Price changes
        for period, key in [("24h", "price_change_percentage_24h"),
                           ("7d", "price_change_percentage_7d"),
                           ("30d", "price_change_percentage_30d")]:
            change = market.get(key, 0)
            if change is not None:
                lines.append(f"{period} Change: {change:+.2f}%")

        # Supply
        supply = market.get("circulating_supply", 0)
        if supply:
            lines.append(f"Circulating Supply: {supply:,.0f} {data.get('symbol', '').upper()}")

        total_supply = market.get("total_supply", 0)
        if total_supply:
            lines.append(f"Total Supply: {total_supply:,.0f}")

        max_supply = market.get("max_supply", 0)
        if max_supply:
            lines.append(f"Max Supply: {max_supply:,.0f}")

        # ATH/ATL
        ath = market.get("ath", {}).get("usd", 0)
        ath_change = market.get("ath_change_percentage", {}).get("usd", 0)
        if ath:
            lines.append(f"All-Time High: ${ath:,.2f} ({ath_change:.2f}% from ATH)")

        atl = market.get("atl", {}).get("usd", 0)
        atl_change = market.get("atl_change_percentage", {}).get("usd", 0)
        if atl:
            lines.append(f"All-Time Low: ${atl:,.2f} ({atl_change:+.2f}% from ATL)")

        return "\n".join(lines)

    except httpx.HTTPStatusError as e:
        return f"Error fetching token info: {e.response.status_code}"
    except Exception as e:
        return f"Error fetching token info: {str(e)}"

Step 4: Run test to verify it passes

Run: pytest tradingagents/dataflows/dex/test_coingecko.py -v Expected: PASS

Step 5: Commit

git add tradingagents/dataflows/dex/coingecko_provider.py tradingagents/dataflows/dex/__init__.py
git commit -m "feat(dex): add CoinGecko provider with OHLCV and token info"

Task 3: Add DEX Routing to Interface

Files:

  • Modify: tradingagents/dataflows/interface.py:1-50

Step 1: Read existing interface.py to understand current routing

Run: head -80 tradingagents/dataflows/interface.py

Step 2: Add DEX vendor constants

# Add after existing VENDOR_LIST definition
DEX_VENDOR_LIST = ["coingecko", "defillama", "birdeye"]

# Tool categories for DEX
DEX_TOOLS_CATEGORIES = {
    "core_token_apis": {
        "tools": ["get_token_ohlcv"],
        "default": "coingecko"
    },
    "token_info": {
        "tools": ["get_token_info"],
        "default": "coingecko"
    },
    "technical_indicators": {
        "tools": ["get_token_indicators"],
        "default": "coingecko"
    },
    "defi_fundamentals": {
        "tools": ["get_pool_data", "get_token_info"],
        "default": "defillama"
    },
    "whale_tracking": {
        "tools": ["get_whale_transactions"],
        "default": "birdeye"
    },
}

Step 3: Commit

git add tradingagents/dataflows/interface.py
git commit -m "feat(dex): add DEX vendor routing to interface"

Task 4: Create DEX Tool Wrappers for Agents

Files:

  • Create: tradingagents/agents/utils/dex_tools.py
  • Modify: tradingagents/agents/utils/__init__.py

Step 1: Write the failing test

# tradingagents/agents/utils/test_dex_tools.py
import pytest
from tradingagents.agents.utils.dex_tools import get_token_ohlcv, get_token_info

def test_get_token_ohlcv_is_valid_tool():
    """Verify get_token_ohlcv is a valid LangChain tool."""
    assert hasattr(get_token_ohlcv, 'name')
    assert get_token_ohlcv.name == "get_token_ohlcv"

def test_get_token_info_is_valid_tool():
    """Verify get_token_info is a valid LangChain tool."""
    assert hasattr(get_token_info, 'name')
    assert get_token_info.name == "get_token_info"

Step 2: Run test to verify it fails

Run: pytest tradingagents/agents/utils/test_dex_tools.py -v Expected: FAIL - ModuleNotFoundError

Step 3: Write the tool wrappers

# tradingagents/agents/utils/dex_tools.py
"""DEX tool wrappers for TradingAgents."""

from typing import Annotated
from langchain_core.tools import tool
from tradingagents.dataflows.dex.coingecko_provider import get_coin_ohlcv as _get_coin_ohlcv
from tradingagents.dataflows.dex.coingecko_provider import get_coin_info as _get_coin_info


@tool
def get_token_ohlcv(
    coin_id: Annotated[str, "CoinGecko ID (e.g., solana, bitcoin, ethereum)"],
    vs_currency: Annotated[str, "Target currency (default: usd)"] = "usd",
    days: Annotated[int, "Number of days (1-365, default: 7)"] = 7
) -> str:
    """Get OHLCV (Open-High-Low-Close-Volume) price data for a cryptocurrency token.

    Use this to analyze price movements, trends, and volatility.
    CoinGecko ID examples:
    - solana, bitcoin, ethereum, cardano, polygon, avalanche-2, chainlink

    Returns formatted OHLC data with price summary.
    """
    import asyncio
    return asyncio.run(_get_coin_ohlcv(coin_id, vs_currency, days))


@tool
def get_token_info(
    coin_id: Annotated[str, "CoinGecko ID (e.g., solana, bitcoin, ethereum)"]
) -> str:
    """Get comprehensive token metadata and market data.

    Includes: current price, market cap, volume, supply, ATH/ATL.
    Use this for fundamental analysis of cryptocurrency tokens.

    CoinGecko ID examples:
    - solana, bitcoin, ethereum, cardano, polygon, avalanche-2, chainlink
    """
    import asyncio
    return asyncio.run(_get_coin_info(coin_id))


@tool
def get_pool_data(
    pool_address: Annotated[str, "DEX pool contract address"],
    chain: Annotated[str, "Blockchain (solana, ethereum, bsc)"] = "solana"
) -> str:
    """Get DEX pool metrics: TVL, volume 24h, fees.

    Note: This requires DeFiLlama provider (Phase 2).
    Currently returns placeholder.
    """
    return "Pool data requires DeFiLlama provider (Phase 2). Use get_token_ohlcv for now."


@tool
def get_whale_transactions(
    token_address: Annotated[str, "Token contract address"],
    chain: Annotated[str, "Blockchain network"] = "solana",
    min_usd: Annotated[float, "Minimum USD value (default: 10000)"] = 10000
) -> str:
    """Track large holder (whale) movements.

    Note: This requires Birdeye provider (Phase 3).
    Currently returns placeholder.
    """
    return "Whale tracking requires Birdeye provider (Phase 3). Use get_token_ohlcv for now."

Step 4: Run test to verify it passes

Run: pytest tradingagents/agents/utils/test_dex_tools.py -v Expected: PASS

Step 5: Commit

git add tradingagents/agents/utils/dex_tools.py
git commit -m "feat(dex): add DEX tool wrappers for agents"

Task 5: Update Default Config for DEX

Files:

  • Modify: tradingagents/default_config.py

Step 1: Write the failing test

# tests/test_config.py
from tradingagents.default_config import DEFAULT_CONFIG

def test_default_config_has_dex_vendors():
    """Verify config supports DEX vendors."""
    assert "data_vendors" in DEFAULT_CONFIG
    assert "core_token_apis" in DEFAULT_CONFIG["data_vendors"]
    assert DEFAULT_CONFIG["data_vendors"]["core_token_apis"] == "coingecko"

def test_default_config_has_chain():
    """Verify config supports default chain."""
    assert "default_chain" in DEFAULT_CONFIG

Step 2: Run test to verify it fails

Run: pytest tests/test_config.py -v Expected: FAIL - KeyError

Step 3: Add DEX config options

Update tradingagents/default_config.py:

DEFAULT_CONFIG = {
    # ... existing settings ...

    # DEX-specific configuration (NEW)
    "data_vendors": {
        # Traditional finance (Stock data - existing)
        "core_stock_apis": "yfinance",
        "technical_indicators": "yfinance",
        "fundamental_data": "yfinance",
        "news_data": "yfinance",

        # DEX/Crypto (NEW - overrides stock data)
        "core_token_apis": "coingecko",
        "token_info": "coingecko",
        "technical_indicators_dex": "coingecko",  # Uses stockstats for calculation
        "defi_fundamentals": "defillama",  # Phase 2
        "whale_tracking": "birdeye",  # Phase 3
    },

    # Default blockchain for DEX operations
    "default_chain": "solana",  # Options: solana, ethereum, bsc, arbitrum, etc.

    # Mode: "stock" or "dex"
    "trading_mode": "stock",  # Start with stock, user switches to "dex"
}

Step 4: Run test to verify it passes

Run: pytest tests/test_config.py -v Expected: PASS

Step 5: Commit

git add tradingagents/default_config.py
git commit -m "feat(dex): add DEX configuration to default config"

Task 6: Update Market Analyst for DEX Mode

Files:

  • Modify: tradingagents/agents/analysts/market_analyst.py:1-80

Step 1: Read existing market analyst

Run: head -100 tradingagents/agents/analysts/market_analyst.py

Step 2: Add DEX mode prompt alternative

# Add after existing SYSTEM_PROMPT
DEX_MARKET_ANALYST_PROMPT = """You are an On-Chain Market Analyst specializing in cryptocurrency and DeFi tokens.

Your role is to analyze:
1. OHLCV data from DEX pools (price, volume, liquidity)
2. Technical indicators (RSI, MACD, Bollinger Bands) calculated from on-chain data
3. Token market structure (TVL, volume ratios)

When analyzing, consider:
- Price momentum and trend direction
- Volume anomalies (unusual buying/selling)
- Liquidity depth implications
- Comparison to similar tokens in the ecosystem

Provide insights in a structured format that helps traders make informed decisions.
"""

Step 3: Modify the agent to support both modes

In the MarketAnalyst class, update the initialization to accept trading_mode and select appropriate prompt.

Step 4: Commit

git add tradingagents/agents/analysts/market_analyst.py
git commit -m "feat(dex): add DEX mode prompt to market analyst"

Phase 2: DeFiLlama Provider (Next Iteration)

After Phase 1 is verified working:

Task 7: Add DeFiLlama Provider

Files:

  • Create: tradingagents/dataflows/dex/defillama_provider.py
  • Modify: tradingagents/dataflows/dex/__init__.py
# Minimal implementation required:
# - get_tvl(protocol_name: str) -> str
# - get_pool_data(pool_address: str, chain: str) -> str
# - get_chain_volumes(chain: str) -> str

Phase 3: Birdeye Provider (Next Iteration)

Task 8: Add Birdeye Provider

Files:

  • Create: tradingagents/dataflows/dex/birdeye_provider.py
  • Modify: tradingagents/dataflows/dex/__init__.py
# Minimal implementation required:
# - get_whale_transactions(token_address: str, chain: str, min_usd: float) -> str
# - get_token_security(token_address: str, chain: str) -> str

Verification Commands

Phase 1 Verification

# Test CoinGecko provider directly
python -c "
import asyncio
from tradingagents.dataflows.dex.coingecko_provider import get_coin_ohlcv, get_coin_info

async def test():
    ohlc = await get_coin_ohlcv('solana', 'usd', 7)
    print('OHLCV:', ohlc[:500])

    info = await get_coin_info('solana')
    print('INFO:', info[:500])

asyncio.run(test())
"

# Run full pipeline test
python -c "
from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG

config = DEFAULT_CONFIG.copy()
config['trading_mode'] = 'dex'
config['default_chain'] = 'solana'

ta = TradingAgentsGraph(debug=True, config=config)
state, decision = ta.propagate('solana', '2026-03-01')
print('Decision:', decision)
"

Plan Complete

Saved to: docs/plans/2026-03-11-dex-data-layer.md

Two execution options:

  1. Subagent-Driven (this session) - I dispatch fresh subagent per task, review between tasks, fast iteration

  2. Parallel Session (separate) - Open new session with executing-plans, batch execution with checkpoints

Which approach?