├── src ├── utils │ ├── __init__.py │ └── config.py ├── trader │ ├── __init__.py │ └── orchestrator.py ├── markets │ ├── __init__.py │ ├── drift.py │ ├── olab.py │ ├── moonopol.py │ ├── augur.py │ ├── polkamarkets.py │ ├── hedgehog.py │ ├── polymarket.py │ └── myriad.py ├── blockchains │ ├── __init__.py │ ├── bnb_chain.py │ ├── polygon.py │ ├── ethereum.py │ └── solana.py ├── core │ ├── __init__.py │ └── interfaces.py └── __init__.py ├── requirements.txt ├── .gitignore ├── main.py └── README.md /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """Utility functions.""" 2 | -------------------------------------------------------------------------------- /src/trader/__init__.py: -------------------------------------------------------------------------------- 1 | """Unified trading interface.""" 2 | -------------------------------------------------------------------------------- /src/markets/__init__.py: -------------------------------------------------------------------------------- 1 | """Prediction market platform connectors.""" 2 | -------------------------------------------------------------------------------- /src/blockchains/__init__.py: -------------------------------------------------------------------------------- 1 | """Blockchain connectors for various networks.""" 2 | -------------------------------------------------------------------------------- /src/core/__init__.py: -------------------------------------------------------------------------------- 1 | """Core interfaces and abstractions for prediction market trading.""" 2 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Prediction Market Trading System 3 | A unified interface for trading across multiple blockchain-based prediction markets. 4 | """ 5 | 6 | __version__ = "1.0.0" 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Core dependencies 2 | web3>=6.0.0 3 | solana>=0.30.0 4 | solders>=0.18.0 5 | anchorpy>=0.18.0 6 | eth-account>=0.9.0 7 | python-dotenv>=1.0.0 8 | requests>=2.31.0 9 | aiohttp>=3.9.0 10 | 11 | # Ethereum/Polygon/BNB Chain 12 | eth-abi>=4.0.0 13 | eth-utils>=2.3.0 14 | eth-typing>=3.5.0 15 | 16 | # Solana 17 | base58>=2.1.1 18 | solana-py>=0.30.0 19 | 20 | # Utilities 21 | pydantic>=2.0.0 22 | pydantic-settings>=2.0.0 23 | loguru>=0.7.0 24 | typing-extensions>=4.8.0 25 | 26 | # API clients 27 | httpx>=0.25.0 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Virtual Environment 24 | venv/ 25 | ENV/ 26 | env/ 27 | 28 | # IDE 29 | .vscode/ 30 | .idea/ 31 | *.swp 32 | *.swo 33 | 34 | # Environment variables 35 | .env 36 | 37 | # Logs 38 | *.log 39 | trading.log 40 | 41 | # OS 42 | .DS_Store 43 | Thumbs.db 44 | -------------------------------------------------------------------------------- /src/blockchains/bnb_chain.py: -------------------------------------------------------------------------------- 1 | """BNB Chain connector (EVM-compatible).""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, Any, Optional 5 | from web3 import Web3 6 | from web3.middleware import geth_poa_middleware 7 | from loguru import logger 8 | 9 | from .ethereum import EthereumConnector 10 | 11 | 12 | class BNBChainConnector(EthereumConnector): 13 | """BNB Chain connector (EVM-compatible with Ethereum).""" 14 | 15 | def __init__(self, rpc_url: Optional[str] = None, private_key: Optional[str] = None): 16 | """ 17 | Initialize BNB Chain connector. 18 | 19 | Args: 20 | rpc_url: RPC endpoint URL (defaults to public RPC) 21 | private_key: Optional private key for signing transactions 22 | """ 23 | rpc_url = rpc_url or "https://bsc-dataseed1.binance.org" 24 | super().__init__(rpc_url, private_key) 25 | self.chain_id = 56 26 | 27 | def connect(self) -> bool: 28 | """Connect to BNB Chain network.""" 29 | result = super().connect() 30 | if result: 31 | logger.info("Connected to BNB Chain network") 32 | return result 33 | -------------------------------------------------------------------------------- /src/blockchains/polygon.py: -------------------------------------------------------------------------------- 1 | """Polygon blockchain connector (EVM-compatible).""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, Any, Optional 5 | from web3 import Web3 6 | from web3.middleware import geth_poa_middleware 7 | from eth_account import Account 8 | from loguru import logger 9 | 10 | from .ethereum import EthereumConnector 11 | 12 | 13 | class PolygonConnector(EthereumConnector): 14 | """Polygon blockchain connector (EVM-compatible with Ethereum).""" 15 | 16 | def __init__(self, rpc_url: Optional[str] = None, private_key: Optional[str] = None): 17 | """ 18 | Initialize Polygon connector. 19 | 20 | Args: 21 | rpc_url: RPC endpoint URL (defaults to public RPC) 22 | private_key: Optional private key for signing transactions 23 | """ 24 | rpc_url = rpc_url or "https://polygon-rpc.com" 25 | super().__init__(rpc_url, private_key) 26 | self.chain_id = 137 27 | 28 | def connect(self) -> bool: 29 | """Connect to Polygon network.""" 30 | result = super().connect() 31 | if result: 32 | logger.info("Connected to Polygon network") 33 | return result 34 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """Main entry point for prediction market trading system.""" 2 | 3 | import asyncio 4 | from decimal import Decimal 5 | from loguru import logger 6 | from src.trader.orchestrator import PredictionMarketTrader 7 | from src.utils.config import load_config 8 | from src.core.interfaces import PositionSide, MarketStatus 9 | 10 | 11 | def main(): 12 | """Main function demonstrating usage.""" 13 | # Load configuration 14 | config = load_config() 15 | 16 | # Initialize trader 17 | trader = PredictionMarketTrader(config) 18 | 19 | # List available platforms 20 | platforms = trader.list_platforms() 21 | logger.info(f"Available platforms: {', '.join(platforms)}") 22 | 23 | # Example: Get all markets 24 | logger.info("Fetching markets from all platforms...") 25 | markets = trader.get_all_markets(limit=10) 26 | logger.info(f"Found {len(markets)} markets") 27 | 28 | # Display sample markets 29 | for market in markets[:5]: 30 | logger.info(f"\nPlatform: {market.platform}") 31 | logger.info(f"Question: {market.question}") 32 | logger.info(f"Status: {market.status.value}") 33 | logger.info(f"Volume: {market.volume}") 34 | 35 | # Example: Get positions (requires user address) 36 | # user_address = "0x..." # Replace with actual address 37 | # positions = trader.get_all_positions(user_address) 38 | # logger.info(f"Found {len(positions)} positions") 39 | 40 | # Example: Create a position (requires proper setup) 41 | # tx_hash = trader.create_position( 42 | # platform="polymarket", 43 | # market_id="0x...", 44 | # side=PositionSide.YES, 45 | # amount=Decimal("100.0") 46 | # ) 47 | # logger.info(f"Created position: {tx_hash}") 48 | 49 | 50 | if __name__ == "__main__": 51 | logger.add("trading.log", rotation="10 MB", level="DEBUG") 52 | main() 53 | -------------------------------------------------------------------------------- /src/utils/config.py: -------------------------------------------------------------------------------- 1 | """Configuration management utilities.""" 2 | 3 | import os 4 | from typing import Dict, Any, Optional 5 | from pathlib import Path 6 | from dotenv import load_dotenv 7 | from pydantic_settings import BaseSettings 8 | 9 | 10 | class BlockchainConfig(BaseSettings): 11 | """Blockchain configuration.""" 12 | rpc_url: Optional[str] = None 13 | private_key: Optional[str] = None 14 | 15 | class Config: 16 | env_prefix = "BLOCKCHAIN_" 17 | 18 | 19 | class TraderConfig(BaseSettings): 20 | """Trader configuration.""" 21 | blockchains: Dict[str, Dict[str, str]] = {} 22 | 23 | def __init__(self, **kwargs): 24 | super().__init__(**kwargs) 25 | # Load from environment variables if not provided 26 | self._load_from_env() 27 | 28 | def _load_from_env(self): 29 | """Load configuration from environment variables.""" 30 | # Ethereum 31 | if os.getenv("ETHEREUM_RPC_URL"): 32 | self.blockchains["ethereum"] = { 33 | "rpc_url": os.getenv("ETHEREUM_RPC_URL"), 34 | "private_key": os.getenv("ETHEREUM_PRIVATE_KEY") 35 | } 36 | 37 | # Polygon 38 | if os.getenv("POLYGON_RPC_URL"): 39 | self.blockchains["polygon"] = { 40 | "rpc_url": os.getenv("POLYGON_RPC_URL"), 41 | "private_key": os.getenv("POLYGON_PRIVATE_KEY") 42 | } 43 | 44 | # Solana 45 | if os.getenv("SOLANA_RPC_URL"): 46 | self.blockchains["solana"] = { 47 | "rpc_url": os.getenv("SOLANA_RPC_URL"), 48 | "private_key": os.getenv("SOLANA_PRIVATE_KEY") 49 | } 50 | 51 | # BNB Chain 52 | if os.getenv("BNB_RPC_URL"): 53 | self.blockchains["bnb"] = { 54 | "rpc_url": os.getenv("BNB_RPC_URL"), 55 | "private_key": os.getenv("BNB_PRIVATE_KEY") 56 | } 57 | 58 | 59 | def load_config(config_path: Optional[str] = None) -> Dict[str, Any]: 60 | """ 61 | Load configuration from file or environment. 62 | 63 | Args: 64 | config_path: Optional path to config file 65 | 66 | Returns: 67 | Configuration dictionary 68 | """ 69 | # Load .env file if it exists 70 | env_path = Path(config_path) if config_path else Path(".env") 71 | if env_path.exists(): 72 | load_dotenv(env_path) 73 | 74 | config = TraderConfig() 75 | return {"blockchains": config.blockchains} 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi-Chain Prediction Market Trading System 2 | 3 | A unified Python framework for trading across multiple prediction market platforms on different blockchains. This project provides a single interface to interact with Polymarket, Augur, Moonopol, Myriad Markets, Drift BET, O.LAB, Polkamarkets, and Hedgehog Markets. 4 | 5 | ## Features 6 | 7 | - **Multi-Blockchain Support**: Trade on Ethereum, Polygon, Solana, and BNB Chain 8 | - **Unified Interface**: Single API to interact with all supported prediction markets 9 | - **Platform Support**: 10 | - **Polymarket** (Polygon) 11 | - **Augur** (Ethereum) 12 | - **Moonopol** (Solana) 13 | - **Myriad Markets** (Ethereum/Polygon) 14 | - **Drift BET** (Solana) 15 | - **O.LAB** (BNB Chain) 16 | - **Polkamarkets** (Polygon) 17 | - **Hedgehog Markets** (Ethereum) 18 | 19 | - **Core Functionality**: 20 | - Fetch markets and market data 21 | - Get orderbooks and prices 22 | - Create and close positions 23 | - Check balances across chains 24 | - Monitor positions across platforms 25 | 26 | ## Architecture 27 | 28 | The project follows a modular architecture with clear separation of concerns: 29 | 30 | ``` 31 | src/ 32 | ├── core/ # Core interfaces and data models 33 | │ └── interfaces.py # Abstract base classes and data structures 34 | ├── blockchains/ # Blockchain connectors 35 | │ ├── ethereum.py # Ethereum connector 36 | │ ├── polygon.py # Polygon connector (EVM-compatible) 37 | │ ├── solana.py # Solana connector 38 | │ └── bnb_chain.py # BNB Chain connector (EVM-compatible) 39 | ├── markets/ # Prediction market platform connectors 40 | │ ├── polymarket.py 41 | │ ├── augur.py 42 | │ ├── moonopol.py 43 | │ ├── myriad.py 44 | │ ├── drift.py 45 | │ ├── olab.py 46 | │ ├── polkamarkets.py 47 | │ └── hedgehog.py 48 | ├── trader/ # Unified trading orchestrator 49 | │ └── orchestrator.py 50 | └── utils/ # Utilities 51 | └── config.py # Configuration management 52 | ``` 53 | 54 | ## Installation 55 | 56 | 1. **Clone the repository**: 57 | ```bash 58 | git clone 59 | cd git 60 | ``` 61 | 62 | 2. **Create a virtual environment** (recommended): 63 | ```bash 64 | python -m venv venv 65 | 66 | # On Windows 67 | venv\Scripts\activate 68 | 69 | # On Linux/Mac 70 | source venv/bin/activate 71 | ``` 72 | 73 | 3. **Install dependencies**: 74 | ```bash 75 | pip install -r requirements.txt 76 | ``` 77 | 78 | 4. **Set up environment variables**: 79 | ```bash 80 | # Copy the example environment file 81 | cp .env.example .env 82 | 83 | # Edit .env with your RPC URLs and private keys 84 | ``` 85 | 86 | ## Configuration 87 | 88 | Create a `.env` file in the project root with the following variables: 89 | 90 | ```env 91 | # Ethereum Configuration 92 | ETHEREUM_RPC_URL=https://mainnet.infura.io/v3/YOUR_PROJECT_ID 93 | ETHEREUM_PRIVATE_KEY=your_ethereum_private_key_here 94 | 95 | # Polygon Configuration 96 | POLYGON_RPC_URL=https://polygon-rpc.com 97 | POLYGON_PRIVATE_KEY=your_polygon_private_key_here 98 | 99 | # Solana Configuration 100 | SOLANA_RPC_URL=https://api.mainnet-beta.solana.com 101 | SOLANA_PRIVATE_KEY=your_base58_encoded_solana_private_key_here 102 | 103 | # BNB Chain Configuration 104 | BNB_RPC_URL=https://bsc-dataseed.binance.org 105 | BNB_PRIVATE_KEY=your_bnb_chain_private_key_here 106 | ``` 107 | 108 | **Security Note**: Never commit your `.env` file or private keys to version control. The `.gitignore` file is configured to exclude `.env` files. 109 | 110 | ### Getting RPC URLs 111 | 112 | - **Ethereum**: Use Infura, Alchemy, or QuickNode 113 | - **Polygon**: Use Polygon RPC endpoints or Alchemy 114 | - **Solana**: Use public RPC endpoints or Helius, QuickNode 115 | - **BNB Chain**: Use Binance public RPC or QuickNode 116 | 117 | ## Support 118 | 119 | For issues, questions, or contributions, please open an issue on the repository or 📞 [soulcrancerdev](https://t.me/soulcrancerdev). 120 | -------------------------------------------------------------------------------- /src/core/interfaces.py: -------------------------------------------------------------------------------- 1 | """Core interfaces for prediction market trading.""" 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import Dict, List, Optional, Any 5 | from decimal import Decimal 6 | from enum import Enum 7 | from dataclasses import dataclass 8 | from datetime import datetime 9 | 10 | 11 | class MarketStatus(Enum): 12 | """Market status enumeration.""" 13 | OPEN = "open" 14 | CLOSED = "closed" 15 | RESOLVED = "resolved" 16 | CANCELLED = "cancelled" 17 | 18 | 19 | class PositionSide(Enum): 20 | """Position side enumeration.""" 21 | YES = "yes" 22 | NO = "no" 23 | 24 | 25 | @dataclass 26 | class Market: 27 | """Represents a prediction market.""" 28 | market_id: str 29 | question: str 30 | description: Optional[str] 31 | outcomes: List[str] 32 | status: MarketStatus 33 | end_date: Optional[datetime] 34 | volume: Optional[Decimal] 35 | liquidity: Optional[Decimal] 36 | platform: str 37 | blockchain: str 38 | metadata: Dict[str, Any] 39 | 40 | 41 | @dataclass 42 | class Position: 43 | """Represents a trading position.""" 44 | position_id: str 45 | market_id: str 46 | side: PositionSide 47 | shares: Decimal 48 | cost_basis: Decimal 49 | current_value: Decimal 50 | platform: str 51 | blockchain: str 52 | 53 | 54 | @dataclass 55 | class Order: 56 | """Represents a trading order.""" 57 | order_id: str 58 | market_id: str 59 | side: PositionSide 60 | shares: Decimal 61 | price: Decimal 62 | status: str 63 | platform: str 64 | timestamp: datetime 65 | 66 | 67 | class BlockchainConnector(ABC): 68 | """Abstract base class for blockchain connectors.""" 69 | 70 | @abstractmethod 71 | def connect(self) -> bool: 72 | """Connect to the blockchain.""" 73 | pass 74 | 75 | @abstractmethod 76 | def get_balance(self, address: str, token: Optional[str] = None) -> Decimal: 77 | """Get balance for an address.""" 78 | pass 79 | 80 | @abstractmethod 81 | def send_transaction(self, tx_data: Dict[str, Any]) -> str: 82 | """Send a transaction and return tx hash.""" 83 | pass 84 | 85 | @abstractmethod 86 | def wait_for_confirmation(self, tx_hash: str, timeout: int = 300) -> bool: 87 | """Wait for transaction confirmation.""" 88 | pass 89 | 90 | 91 | class PredictionMarketConnector(ABC): 92 | """Abstract base class for prediction market platform connectors.""" 93 | 94 | @abstractmethod 95 | def get_markets( 96 | self, 97 | category: Optional[str] = None, 98 | status: Optional[MarketStatus] = None, 99 | limit: int = 100 100 | ) -> List[Market]: 101 | """Get list of available markets.""" 102 | pass 103 | 104 | @abstractmethod 105 | def get_market(self, market_id: str) -> Optional[Market]: 106 | """Get a specific market by ID.""" 107 | pass 108 | 109 | @abstractmethod 110 | def get_positions(self, user_address: str) -> List[Position]: 111 | """Get user's positions.""" 112 | pass 113 | 114 | @abstractmethod 115 | def create_position( 116 | self, 117 | market_id: str, 118 | side: PositionSide, 119 | amount: Decimal, 120 | max_price: Optional[Decimal] = None 121 | ) -> str: 122 | """Create a new position. Returns transaction hash.""" 123 | pass 124 | 125 | @abstractmethod 126 | def close_position( 127 | self, 128 | position_id: str, 129 | shares: Optional[Decimal] = None 130 | ) -> str: 131 | """Close a position (full or partial). Returns transaction hash.""" 132 | pass 133 | 134 | @abstractmethod 135 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 136 | """Get current price for a market side.""" 137 | pass 138 | 139 | @abstractmethod 140 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 141 | """Get orderbook for a market.""" 142 | pass 143 | -------------------------------------------------------------------------------- /src/blockchains/ethereum.py: -------------------------------------------------------------------------------- 1 | """Ethereum blockchain connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, Any, Optional 5 | from web3 import Web3 6 | from web3.middleware import geth_poa_middleware 7 | from eth_account import Account 8 | from loguru import logger 9 | 10 | from ..core.interfaces import BlockchainConnector 11 | 12 | 13 | class EthereumConnector(BlockchainConnector): 14 | """Ethereum blockchain connector.""" 15 | 16 | def __init__(self, rpc_url: str, private_key: Optional[str] = None): 17 | """ 18 | Initialize Ethereum connector. 19 | 20 | Args: 21 | rpc_url: RPC endpoint URL 22 | private_key: Optional private key for signing transactions 23 | """ 24 | self.rpc_url = rpc_url 25 | self.private_key = private_key 26 | self.w3 = None 27 | self.account = None 28 | 29 | def connect(self) -> bool: 30 | """Connect to Ethereum network.""" 31 | try: 32 | self.w3 = Web3(Web3.HTTPProvider(self.rpc_url)) 33 | 34 | # Add PoA middleware for networks like Polygon 35 | self.w3.middleware_onion.inject(geth_poa_middleware, layer=0) 36 | 37 | if not self.w3.is_connected(): 38 | logger.error("Failed to connect to Ethereum network") 39 | return False 40 | 41 | if self.private_key: 42 | self.account = Account.from_key(self.private_key) 43 | logger.info(f"Connected with address: {self.account.address}") 44 | else: 45 | logger.info("Connected without account (read-only mode)") 46 | 47 | return True 48 | except Exception as e: 49 | logger.error(f"Error connecting to Ethereum: {e}") 50 | return False 51 | 52 | def get_balance(self, address: str, token: Optional[str] = None) -> Decimal: 53 | """Get balance for an address.""" 54 | if not self.w3: 55 | raise RuntimeError("Not connected to blockchain") 56 | 57 | if token: 58 | # ERC-20 token balance 59 | # This is a simplified version - full implementation would use contract ABI 60 | logger.warning("ERC-20 token balance not fully implemented") 61 | return Decimal(0) 62 | else: 63 | # Native ETH balance 64 | balance_wei = self.w3.eth.get_balance(address) 65 | balance_eth = self.w3.from_wei(balance_wei, 'ether') 66 | return Decimal(str(balance_eth)) 67 | 68 | def send_transaction(self, tx_data: Dict[str, Any]) -> str: 69 | """Send a transaction and return tx hash.""" 70 | if not self.w3 or not self.account: 71 | raise RuntimeError("Not connected or no account configured") 72 | 73 | # Prepare transaction 74 | nonce = self.w3.eth.get_transaction_count(self.account.address) 75 | tx = { 76 | 'nonce': nonce, 77 | 'to': tx_data.get('to'), 78 | 'value': tx_data.get('value', 0), 79 | 'gas': tx_data.get('gas', 21000), 80 | 'gasPrice': tx_data.get('gasPrice') or self.w3.eth.gas_price, 81 | 'data': tx_data.get('data', b''), 82 | } 83 | 84 | # Sign and send 85 | signed_tx = self.account.sign_transaction(tx) 86 | tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) 87 | 88 | return tx_hash.hex() 89 | 90 | def wait_for_confirmation(self, tx_hash: str, timeout: int = 300) -> bool: 91 | """Wait for transaction confirmation.""" 92 | if not self.w3: 93 | raise RuntimeError("Not connected to blockchain") 94 | 95 | try: 96 | receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout) 97 | return receipt.status == 1 98 | except Exception as e: 99 | logger.error(f"Error waiting for confirmation: {e}") 100 | return False 101 | -------------------------------------------------------------------------------- /src/blockchains/solana.py: -------------------------------------------------------------------------------- 1 | """Solana blockchain connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, Any, Optional 5 | from solana.rpc.api import Client 6 | from solana.rpc.commitment import Confirmed 7 | from solders.keypair import Keypair 8 | from solders.pubkey import Pubkey 9 | from solders.system_program import transfer, TransferParams 10 | from solders.transaction import Transaction 11 | from loguru import logger 12 | import base58 13 | 14 | from ..core.interfaces import BlockchainConnector 15 | 16 | 17 | class SolanaConnector(BlockchainConnector): 18 | """Solana blockchain connector.""" 19 | 20 | def __init__(self, rpc_url: str, private_key: Optional[str] = None): 21 | """ 22 | Initialize Solana connector. 23 | 24 | Args: 25 | rpc_url: RPC endpoint URL 26 | private_key: Optional base58-encoded private key for signing transactions 27 | """ 28 | self.rpc_url = rpc_url 29 | self.private_key = private_key 30 | self.client = None 31 | self.keypair = None 32 | 33 | def connect(self) -> bool: 34 | """Connect to Solana network.""" 35 | try: 36 | self.client = Client(self.rpc_url) 37 | 38 | # Test connection 39 | version = self.client.get_version() 40 | if not version: 41 | logger.error("Failed to connect to Solana network") 42 | return False 43 | 44 | if self.private_key: 45 | try: 46 | key_bytes = base58.b58decode(self.private_key) 47 | self.keypair = Keypair.from_bytes(key_bytes) 48 | logger.info(f"Connected with address: {self.keypair.pubkey()}") 49 | except Exception as e: 50 | logger.error(f"Error loading keypair: {e}") 51 | return False 52 | else: 53 | logger.info("Connected without keypair (read-only mode)") 54 | 55 | return True 56 | except Exception as e: 57 | logger.error(f"Error connecting to Solana: {e}") 58 | return False 59 | 60 | def get_balance(self, address: str, token: Optional[str] = None) -> Decimal: 61 | """Get balance for an address.""" 62 | if not self.client: 63 | raise RuntimeError("Not connected to blockchain") 64 | 65 | if token: 66 | # SPL token balance 67 | logger.warning("SPL token balance not fully implemented") 68 | return Decimal(0) 69 | else: 70 | # Native SOL balance 71 | pubkey = Pubkey.from_string(address) 72 | response = self.client.get_balance(pubkey) 73 | if response.value is None: 74 | return Decimal(0) 75 | # Convert lamports to SOL (1 SOL = 1e9 lamports) 76 | balance_sol = Decimal(response.value) / Decimal(1e9) 77 | return balance_sol 78 | 79 | def send_transaction(self, tx_data: Dict[str, Any]) -> str: 80 | """Send a transaction and return tx signature.""" 81 | if not self.client or not self.keypair: 82 | raise RuntimeError("Not connected or no keypair configured") 83 | 84 | # Create transaction 85 | transaction = Transaction() 86 | 87 | # Add instructions (simplified - full implementation would handle various instruction types) 88 | if 'to' in tx_data and 'amount' in tx_data: 89 | to_pubkey = Pubkey.from_string(tx_data['to']) 90 | lamports = int(tx_data['amount'] * 1e9) # Convert SOL to lamports 91 | transfer_ix = transfer( 92 | TransferParams( 93 | from_pubkey=self.keypair.pubkey(), 94 | to_pubkey=to_pubkey, 95 | lamports=lamports 96 | ) 97 | ) 98 | transaction.add(transfer_ix) 99 | 100 | # Sign and send 101 | response = self.client.send_transaction(transaction, self.keypair) 102 | return response.value 103 | 104 | def wait_for_confirmation(self, tx_signature: str, timeout: int = 300) -> bool: 105 | """Wait for transaction confirmation.""" 106 | if not self.client: 107 | raise RuntimeError("Not connected to blockchain") 108 | 109 | try: 110 | import time 111 | start_time = time.time() 112 | while time.time() - start_time < timeout: 113 | response = self.client.get_signature_statuses([tx_signature]) 114 | if response.value and response.value[0]: 115 | status = response.value[0] 116 | if status.confirmation_status == Confirmed: 117 | return True 118 | time.sleep(1) 119 | return False 120 | except Exception as e: 121 | logger.error(f"Error waiting for confirmation: {e}") 122 | return False 123 | -------------------------------------------------------------------------------- /src/markets/drift.py: -------------------------------------------------------------------------------- 1 | """Drift BET connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, List, Optional, Any 5 | from datetime import datetime 6 | from loguru import logger 7 | import httpx 8 | 9 | from ..core.interfaces import ( 10 | PredictionMarketConnector, 11 | Market, 12 | Position, 13 | PositionSide, 14 | MarketStatus 15 | ) 16 | from ..blockchains.solana import SolanaConnector 17 | 18 | 19 | class DriftBETConnector(PredictionMarketConnector): 20 | """Drift BET connector.""" 21 | 22 | def __init__(self, blockchain_connector: SolanaConnector, api_url: str = "https://api.drift.trade"): 23 | """ 24 | Initialize Drift BET connector. 25 | 26 | Args: 27 | blockchain_connector: Solana blockchain connector instance 28 | api_url: Drift API URL 29 | """ 30 | self.blockchain = blockchain_connector 31 | self.api_url = api_url 32 | self.client = httpx.AsyncClient(timeout=30.0) 33 | 34 | async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: 35 | """Make GET request to API.""" 36 | try: 37 | response = await self.client.get( 38 | f"{self.api_url}/{endpoint}", 39 | params=params or {} 40 | ) 41 | response.raise_for_status() 42 | return response.json() 43 | except Exception as e: 44 | logger.error(f"API request failed: {e}") 45 | raise 46 | 47 | def get_markets( 48 | self, 49 | category: Optional[str] = None, 50 | status: Optional[MarketStatus] = None, 51 | limit: int = 100 52 | ) -> List[Market]: 53 | """Get list of available markets.""" 54 | import asyncio 55 | 56 | params = {"limit": limit} 57 | if category: 58 | params["category"] = category 59 | 60 | try: 61 | data = asyncio.run(self._get("bet/markets", params)) 62 | markets = [] 63 | 64 | for item in data.get("markets", []): 65 | market_status = MarketStatus.OPEN 66 | if item.get("closed"): 67 | market_status = MarketStatus.CLOSED 68 | if item.get("resolved"): 69 | market_status = MarketStatus.RESOLVED 70 | 71 | markets.append(Market( 72 | market_id=item.get("id", ""), 73 | question=item.get("question", ""), 74 | description=item.get("description"), 75 | outcomes=["YES", "NO"], 76 | status=market_status, 77 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 78 | volume=Decimal(str(item.get("volume", 0))), 79 | liquidity=Decimal(str(item.get("liquidity", 0))), 80 | platform="drift", 81 | blockchain="solana", 82 | metadata=item 83 | )) 84 | 85 | return markets 86 | except Exception as e: 87 | logger.error(f"Error fetching markets: {e}") 88 | return [] 89 | 90 | def get_market(self, market_id: str) -> Optional[Market]: 91 | """Get a specific market by ID.""" 92 | import asyncio 93 | 94 | try: 95 | data = asyncio.run(self._get(f"bet/markets/{market_id}")) 96 | item = data.get("market", {}) 97 | 98 | market_status = MarketStatus.OPEN 99 | if item.get("closed"): 100 | market_status = MarketStatus.CLOSED 101 | if item.get("resolved"): 102 | market_status = MarketStatus.RESOLVED 103 | 104 | return Market( 105 | market_id=item.get("id", market_id), 106 | question=item.get("question", ""), 107 | description=item.get("description"), 108 | outcomes=["YES", "NO"], 109 | status=market_status, 110 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 111 | volume=Decimal(str(item.get("volume", 0))), 112 | liquidity=Decimal(str(item.get("liquidity", 0))), 113 | platform="drift", 114 | blockchain="solana", 115 | metadata=item 116 | ) 117 | except Exception as e: 118 | logger.error(f"Error fetching market {market_id}: {e}") 119 | return None 120 | 121 | def get_positions(self, user_address: str) -> List[Position]: 122 | """Get user's positions.""" 123 | import asyncio 124 | 125 | try: 126 | data = asyncio.run(self._get(f"bet/positions/{user_address}")) 127 | positions = [] 128 | 129 | for item in data.get("positions", []): 130 | positions.append(Position( 131 | position_id=item.get("id", ""), 132 | market_id=item.get("marketId", ""), 133 | side=PositionSide.YES if item.get("side") == "YES" else PositionSide.NO, 134 | shares=Decimal(str(item.get("shares", 0))), 135 | cost_basis=Decimal(str(item.get("costBasis", 0))), 136 | current_value=Decimal(str(item.get("currentValue", 0))), 137 | platform="drift", 138 | blockchain="solana" 139 | )) 140 | 141 | return positions 142 | except Exception as e: 143 | logger.error(f"Error fetching positions: {e}") 144 | return [] 145 | 146 | def create_position( 147 | self, 148 | market_id: str, 149 | side: PositionSide, 150 | amount: Decimal, 151 | max_price: Optional[Decimal] = None 152 | ) -> str: 153 | """Create a new position. Returns transaction hash.""" 154 | logger.warning("create_position not fully implemented - requires contract integration") 155 | raise NotImplementedError("Contract integration required") 156 | 157 | def close_position( 158 | self, 159 | position_id: str, 160 | shares: Optional[Decimal] = None 161 | ) -> str: 162 | """Close a position (full or partial). Returns transaction hash.""" 163 | logger.warning("close_position not fully implemented - requires contract integration") 164 | raise NotImplementedError("Contract integration required") 165 | 166 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 167 | """Get current price for a market side.""" 168 | import asyncio 169 | 170 | try: 171 | data = asyncio.run(self._get(f"bet/markets/{market_id}/price")) 172 | prices = data.get("data", {}) 173 | side_str = "YES" if side == PositionSide.YES else "NO" 174 | return Decimal(str(prices.get(side_str, 0))) 175 | except Exception as e: 176 | logger.error(f"Error fetching price: {e}") 177 | return Decimal(0) 178 | 179 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 180 | """Get orderbook for a market.""" 181 | import asyncio 182 | 183 | try: 184 | data = asyncio.run(self._get(f"bet/markets/{market_id}/orderbook")) 185 | return data.get("data", {}) 186 | except Exception as e: 187 | logger.error(f"Error fetching orderbook: {e}") 188 | return {} 189 | -------------------------------------------------------------------------------- /src/markets/olab.py: -------------------------------------------------------------------------------- 1 | """O.LAB connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, List, Optional, Any 5 | from datetime import datetime 6 | from loguru import logger 7 | import httpx 8 | 9 | from ..core.interfaces import ( 10 | PredictionMarketConnector, 11 | Market, 12 | Position, 13 | PositionSide, 14 | MarketStatus 15 | ) 16 | from ..blockchains.bnb_chain import BNBChainConnector 17 | 18 | 19 | class OLABConnector(PredictionMarketConnector): 20 | """O.LAB connector.""" 21 | 22 | def __init__(self, blockchain_connector: BNBChainConnector, api_url: str = "https://api.olab.finance"): 23 | """ 24 | Initialize O.LAB connector. 25 | 26 | Args: 27 | blockchain_connector: BNB Chain blockchain connector instance 28 | api_url: O.LAB API URL 29 | """ 30 | self.blockchain = blockchain_connector 31 | self.api_url = api_url 32 | self.client = httpx.AsyncClient(timeout=30.0) 33 | 34 | async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: 35 | """Make GET request to API.""" 36 | try: 37 | response = await self.client.get( 38 | f"{self.api_url}/{endpoint}", 39 | params=params or {} 40 | ) 41 | response.raise_for_status() 42 | return response.json() 43 | except Exception as e: 44 | logger.error(f"API request failed: {e}") 45 | raise 46 | 47 | def get_markets( 48 | self, 49 | category: Optional[str] = None, 50 | status: Optional[MarketStatus] = None, 51 | limit: int = 100 52 | ) -> List[Market]: 53 | """Get list of available markets.""" 54 | import asyncio 55 | 56 | params = {"limit": limit} 57 | if category: 58 | params["category"] = category 59 | 60 | try: 61 | data = asyncio.run(self._get("markets", params)) 62 | markets = [] 63 | 64 | for item in data.get("markets", []): 65 | market_status = MarketStatus.OPEN 66 | if item.get("closed"): 67 | market_status = MarketStatus.CLOSED 68 | if item.get("resolved"): 69 | market_status = MarketStatus.RESOLVED 70 | 71 | markets.append(Market( 72 | market_id=item.get("id", ""), 73 | question=item.get("question", ""), 74 | description=item.get("description"), 75 | outcomes=item.get("outcomes", ["YES", "NO"]), 76 | status=market_status, 77 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 78 | volume=Decimal(str(item.get("volume", 0))), 79 | liquidity=Decimal(str(item.get("liquidity", 0))), 80 | platform="olab", 81 | blockchain="bnb", 82 | metadata=item 83 | )) 84 | 85 | return markets 86 | except Exception as e: 87 | logger.error(f"Error fetching markets: {e}") 88 | return [] 89 | 90 | def get_market(self, market_id: str) -> Optional[Market]: 91 | """Get a specific market by ID.""" 92 | import asyncio 93 | 94 | try: 95 | data = asyncio.run(self._get(f"markets/{market_id}")) 96 | item = data.get("market", {}) 97 | 98 | market_status = MarketStatus.OPEN 99 | if item.get("closed"): 100 | market_status = MarketStatus.CLOSED 101 | if item.get("resolved"): 102 | market_status = MarketStatus.RESOLVED 103 | 104 | return Market( 105 | market_id=item.get("id", market_id), 106 | question=item.get("question", ""), 107 | description=item.get("description"), 108 | outcomes=item.get("outcomes", ["YES", "NO"]), 109 | status=market_status, 110 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 111 | volume=Decimal(str(item.get("volume", 0))), 112 | liquidity=Decimal(str(item.get("liquidity", 0))), 113 | platform="olab", 114 | blockchain="bnb", 115 | metadata=item 116 | ) 117 | except Exception as e: 118 | logger.error(f"Error fetching market {market_id}: {e}") 119 | return None 120 | 121 | def get_positions(self, user_address: str) -> List[Position]: 122 | """Get user's positions.""" 123 | import asyncio 124 | 125 | try: 126 | data = asyncio.run(self._get(f"users/{user_address}/positions")) 127 | positions = [] 128 | 129 | for item in data.get("positions", []): 130 | positions.append(Position( 131 | position_id=item.get("id", ""), 132 | market_id=item.get("marketId", ""), 133 | side=PositionSide.YES if item.get("side") == "YES" else PositionSide.NO, 134 | shares=Decimal(str(item.get("shares", 0))), 135 | cost_basis=Decimal(str(item.get("costBasis", 0))), 136 | current_value=Decimal(str(item.get("currentValue", 0))), 137 | platform="olab", 138 | blockchain="bnb" 139 | )) 140 | 141 | return positions 142 | except Exception as e: 143 | logger.error(f"Error fetching positions: {e}") 144 | return [] 145 | 146 | def create_position( 147 | self, 148 | market_id: str, 149 | side: PositionSide, 150 | amount: Decimal, 151 | max_price: Optional[Decimal] = None 152 | ) -> str: 153 | """Create a new position. Returns transaction hash.""" 154 | logger.warning("create_position not fully implemented - requires contract integration") 155 | raise NotImplementedError("Contract integration required") 156 | 157 | def close_position( 158 | self, 159 | position_id: str, 160 | shares: Optional[Decimal] = None 161 | ) -> str: 162 | """Close a position (full or partial). Returns transaction hash.""" 163 | logger.warning("close_position not fully implemented - requires contract integration") 164 | raise NotImplementedError("Contract integration required") 165 | 166 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 167 | """Get current price for a market side.""" 168 | import asyncio 169 | 170 | try: 171 | data = asyncio.run(self._get(f"markets/{market_id}/price")) 172 | prices = data.get("data", {}) 173 | side_str = "YES" if side == PositionSide.YES else "NO" 174 | return Decimal(str(prices.get(side_str, 0))) 175 | except Exception as e: 176 | logger.error(f"Error fetching price: {e}") 177 | return Decimal(0) 178 | 179 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 180 | """Get orderbook for a market.""" 181 | import asyncio 182 | 183 | try: 184 | data = asyncio.run(self._get(f"markets/{market_id}/orderbook")) 185 | return data.get("data", {}) 186 | except Exception as e: 187 | logger.error(f"Error fetching orderbook: {e}") 188 | return {} 189 | -------------------------------------------------------------------------------- /src/markets/moonopol.py: -------------------------------------------------------------------------------- 1 | """Moonopol connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, List, Optional, Any 5 | from datetime import datetime 6 | from loguru import logger 7 | import httpx 8 | 9 | from ..core.interfaces import ( 10 | PredictionMarketConnector, 11 | Market, 12 | Position, 13 | PositionSide, 14 | MarketStatus 15 | ) 16 | from ..blockchains.solana import SolanaConnector 17 | 18 | 19 | class MoonopolConnector(PredictionMarketConnector): 20 | """Moonopol prediction market connector.""" 21 | 22 | def __init__(self, blockchain_connector: SolanaConnector, api_url: str = "https://api.moonopol.com"): 23 | """ 24 | Initialize Moonopol connector. 25 | 26 | Args: 27 | blockchain_connector: Solana blockchain connector instance 28 | api_url: Moonopol API URL 29 | """ 30 | self.blockchain = blockchain_connector 31 | self.api_url = api_url 32 | self.client = httpx.AsyncClient(timeout=30.0) 33 | 34 | async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: 35 | """Make GET request to API.""" 36 | try: 37 | response = await self.client.get( 38 | f"{self.api_url}/{endpoint}", 39 | params=params or {} 40 | ) 41 | response.raise_for_status() 42 | return response.json() 43 | except Exception as e: 44 | logger.error(f"API request failed: {e}") 45 | raise 46 | 47 | def get_markets( 48 | self, 49 | category: Optional[str] = None, 50 | status: Optional[MarketStatus] = None, 51 | limit: int = 100 52 | ) -> List[Market]: 53 | """Get list of available markets.""" 54 | import asyncio 55 | 56 | params = {"limit": limit} 57 | if category: 58 | params["category"] = category 59 | 60 | try: 61 | data = asyncio.run(self._get("markets", params)) 62 | markets = [] 63 | 64 | for item in data.get("markets", []): 65 | market_status = MarketStatus.OPEN 66 | if item.get("closed"): 67 | market_status = MarketStatus.CLOSED 68 | if item.get("resolved"): 69 | market_status = MarketStatus.RESOLVED 70 | 71 | markets.append(Market( 72 | market_id=item.get("id", ""), 73 | question=item.get("question", ""), 74 | description=item.get("description"), 75 | outcomes=["YES", "NO"], 76 | status=market_status, 77 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 78 | volume=Decimal(str(item.get("volume", 0))), 79 | liquidity=Decimal(str(item.get("liquidity", 0))), 80 | platform="moonopol", 81 | blockchain="solana", 82 | metadata=item 83 | )) 84 | 85 | return markets 86 | except Exception as e: 87 | logger.error(f"Error fetching markets: {e}") 88 | return [] 89 | 90 | def get_market(self, market_id: str) -> Optional[Market]: 91 | """Get a specific market by ID.""" 92 | import asyncio 93 | 94 | try: 95 | data = asyncio.run(self._get(f"markets/{market_id}")) 96 | item = data.get("market", {}) 97 | 98 | market_status = MarketStatus.OPEN 99 | if item.get("closed"): 100 | market_status = MarketStatus.CLOSED 101 | if item.get("resolved"): 102 | market_status = MarketStatus.RESOLVED 103 | 104 | return Market( 105 | market_id=item.get("id", market_id), 106 | question=item.get("question", ""), 107 | description=item.get("description"), 108 | outcomes=["YES", "NO"], 109 | status=market_status, 110 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 111 | volume=Decimal(str(item.get("volume", 0))), 112 | liquidity=Decimal(str(item.get("liquidity", 0))), 113 | platform="moonopol", 114 | blockchain="solana", 115 | metadata=item 116 | ) 117 | except Exception as e: 118 | logger.error(f"Error fetching market {market_id}: {e}") 119 | return None 120 | 121 | def get_positions(self, user_address: str) -> List[Position]: 122 | """Get user's positions.""" 123 | import asyncio 124 | 125 | try: 126 | data = asyncio.run(self._get(f"users/{user_address}/positions")) 127 | positions = [] 128 | 129 | for item in data.get("positions", []): 130 | positions.append(Position( 131 | position_id=item.get("id", ""), 132 | market_id=item.get("marketId", ""), 133 | side=PositionSide.YES if item.get("side") == "YES" else PositionSide.NO, 134 | shares=Decimal(str(item.get("shares", 0))), 135 | cost_basis=Decimal(str(item.get("costBasis", 0))), 136 | current_value=Decimal(str(item.get("currentValue", 0))), 137 | platform="moonopol", 138 | blockchain="solana" 139 | )) 140 | 141 | return positions 142 | except Exception as e: 143 | logger.error(f"Error fetching positions: {e}") 144 | return [] 145 | 146 | def create_position( 147 | self, 148 | market_id: str, 149 | side: PositionSide, 150 | amount: Decimal, 151 | max_price: Optional[Decimal] = None 152 | ) -> str: 153 | """Create a new position. Returns transaction hash.""" 154 | logger.warning("create_position not fully implemented - requires contract integration") 155 | raise NotImplementedError("Contract integration required") 156 | 157 | def close_position( 158 | self, 159 | position_id: str, 160 | shares: Optional[Decimal] = None 161 | ) -> str: 162 | """Close a position (full or partial). Returns transaction hash.""" 163 | logger.warning("close_position not fully implemented - requires contract integration") 164 | raise NotImplementedError("Contract integration required") 165 | 166 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 167 | """Get current price for a market side.""" 168 | import asyncio 169 | 170 | try: 171 | data = asyncio.run(self._get(f"markets/{market_id}/price")) 172 | prices = data.get("data", {}) 173 | side_str = "YES" if side == PositionSide.YES else "NO" 174 | return Decimal(str(prices.get(side_str, 0))) 175 | except Exception as e: 176 | logger.error(f"Error fetching price: {e}") 177 | return Decimal(0) 178 | 179 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 180 | """Get orderbook for a market.""" 181 | import asyncio 182 | 183 | try: 184 | data = asyncio.run(self._get(f"markets/{market_id}/orderbook")) 185 | return data.get("data", {}) 186 | except Exception as e: 187 | logger.error(f"Error fetching orderbook: {e}") 188 | return {} 189 | -------------------------------------------------------------------------------- /src/markets/augur.py: -------------------------------------------------------------------------------- 1 | """Augur connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, List, Optional, Any 5 | from datetime import datetime 6 | from loguru import logger 7 | import httpx 8 | 9 | from ..core.interfaces import ( 10 | PredictionMarketConnector, 11 | Market, 12 | Position, 13 | PositionSide, 14 | MarketStatus 15 | ) 16 | from ..blockchains.ethereum import EthereumConnector 17 | 18 | 19 | class AugurConnector(PredictionMarketConnector): 20 | """Augur prediction market connector.""" 21 | 22 | def __init__(self, blockchain_connector: EthereumConnector, api_url: str = "https://api.augur.net"): 23 | """ 24 | Initialize Augur connector. 25 | 26 | Args: 27 | blockchain_connector: Ethereum blockchain connector instance 28 | api_url: Augur API URL 29 | """ 30 | self.blockchain = blockchain_connector 31 | self.api_url = api_url 32 | self.client = httpx.AsyncClient(timeout=30.0) 33 | 34 | async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: 35 | """Make GET request to API.""" 36 | try: 37 | response = await self.client.get( 38 | f"{self.api_url}/{endpoint}", 39 | params=params or {} 40 | ) 41 | response.raise_for_status() 42 | return response.json() 43 | except Exception as e: 44 | logger.error(f"API request failed: {e}") 45 | raise 46 | 47 | def get_markets( 48 | self, 49 | category: Optional[str] = None, 50 | status: Optional[MarketStatus] = None, 51 | limit: int = 100 52 | ) -> List[Market]: 53 | """Get list of available markets.""" 54 | import asyncio 55 | 56 | params = {"limit": limit} 57 | if category: 58 | params["category"] = category 59 | 60 | try: 61 | data = asyncio.run(self._get("markets", params)) 62 | markets = [] 63 | 64 | for item in data.get("markets", []): 65 | market_status = MarketStatus.OPEN 66 | if item.get("isFinalized"): 67 | market_status = MarketStatus.RESOLVED 68 | if item.get("isInvalid"): 69 | market_status = MarketStatus.CANCELLED 70 | 71 | markets.append(Market( 72 | market_id=item.get("id", ""), 73 | question=item.get("description", ""), 74 | description=item.get("longDescription"), 75 | outcomes=item.get("outcomes", []), 76 | status=market_status, 77 | end_date=datetime.fromtimestamp(item["endDate"]) if item.get("endDate") else None, 78 | volume=Decimal(str(item.get("volume", 0))), 79 | liquidity=Decimal(str(item.get("liquidity", 0))), 80 | platform="augur", 81 | blockchain="ethereum", 82 | metadata=item 83 | )) 84 | 85 | return markets 86 | except Exception as e: 87 | logger.error(f"Error fetching markets: {e}") 88 | return [] 89 | 90 | def get_market(self, market_id: str) -> Optional[Market]: 91 | """Get a specific market by ID.""" 92 | import asyncio 93 | 94 | try: 95 | data = asyncio.run(self._get(f"markets/{market_id}")) 96 | item = data.get("market", {}) 97 | 98 | market_status = MarketStatus.OPEN 99 | if item.get("isFinalized"): 100 | market_status = MarketStatus.RESOLVED 101 | if item.get("isInvalid"): 102 | market_status = MarketStatus.CANCELLED 103 | 104 | return Market( 105 | market_id=item.get("id", market_id), 106 | question=item.get("description", ""), 107 | description=item.get("longDescription"), 108 | outcomes=item.get("outcomes", []), 109 | status=market_status, 110 | end_date=datetime.fromtimestamp(item["endDate"]) if item.get("endDate") else None, 111 | volume=Decimal(str(item.get("volume", 0))), 112 | liquidity=Decimal(str(item.get("liquidity", 0))), 113 | platform="augur", 114 | blockchain="ethereum", 115 | metadata=item 116 | ) 117 | except Exception as e: 118 | logger.error(f"Error fetching market {market_id}: {e}") 119 | return None 120 | 121 | def get_positions(self, user_address: str) -> List[Position]: 122 | """Get user's positions.""" 123 | import asyncio 124 | 125 | try: 126 | data = asyncio.run(self._get(f"users/{user_address}/positions")) 127 | positions = [] 128 | 129 | for item in data.get("positions", []): 130 | positions.append(Position( 131 | position_id=item.get("id", ""), 132 | market_id=item.get("marketId", ""), 133 | side=PositionSide.YES if item.get("outcome") == 0 else PositionSide.NO, 134 | shares=Decimal(str(item.get("shares", 0))), 135 | cost_basis=Decimal(str(item.get("costBasis", 0))), 136 | current_value=Decimal(str(item.get("currentValue", 0))), 137 | platform="augur", 138 | blockchain="ethereum" 139 | )) 140 | 141 | return positions 142 | except Exception as e: 143 | logger.error(f"Error fetching positions: {e}") 144 | return [] 145 | 146 | def create_position( 147 | self, 148 | market_id: str, 149 | side: PositionSide, 150 | amount: Decimal, 151 | max_price: Optional[Decimal] = None 152 | ) -> str: 153 | """Create a new position. Returns transaction hash.""" 154 | logger.warning("create_position not fully implemented - requires contract integration") 155 | raise NotImplementedError("Contract integration required") 156 | 157 | def close_position( 158 | self, 159 | position_id: str, 160 | shares: Optional[Decimal] = None 161 | ) -> str: 162 | """Close a position (full or partial). Returns transaction hash.""" 163 | logger.warning("close_position not fully implemented - requires contract integration") 164 | raise NotImplementedError("Contract integration required") 165 | 166 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 167 | """Get current price for a market side.""" 168 | import asyncio 169 | 170 | try: 171 | data = asyncio.run(self._get(f"markets/{market_id}/prices")) 172 | prices = data.get("prices", {}) 173 | outcome_idx = 0 if side == PositionSide.YES else 1 174 | return Decimal(str(prices.get(str(outcome_idx), 0))) 175 | except Exception as e: 176 | logger.error(f"Error fetching price: {e}") 177 | return Decimal(0) 178 | 179 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 180 | """Get orderbook for a market.""" 181 | import asyncio 182 | 183 | try: 184 | data = asyncio.run(self._get(f"markets/{market_id}/orderbook")) 185 | return data.get("orderbook", {}) 186 | except Exception as e: 187 | logger.error(f"Error fetching orderbook: {e}") 188 | return {} 189 | -------------------------------------------------------------------------------- /src/markets/polkamarkets.py: -------------------------------------------------------------------------------- 1 | """Polkamarkets connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, List, Optional, Any 5 | from datetime import datetime 6 | from loguru import logger 7 | import httpx 8 | 9 | from ..core.interfaces import ( 10 | PredictionMarketConnector, 11 | Market, 12 | Position, 13 | PositionSide, 14 | MarketStatus 15 | ) 16 | from ..blockchains.polygon import PolygonConnector 17 | 18 | 19 | class PolkamarketsConnector(PredictionMarketConnector): 20 | """Polkamarkets connector.""" 21 | 22 | def __init__(self, blockchain_connector: PolygonConnector, api_url: str = "https://api.polkamarkets.com"): 23 | """ 24 | Initialize Polkamarkets connector. 25 | 26 | Args: 27 | blockchain_connector: Polygon blockchain connector instance 28 | api_url: Polkamarkets API URL 29 | """ 30 | self.blockchain = blockchain_connector 31 | self.api_url = api_url 32 | self.client = httpx.AsyncClient(timeout=30.0) 33 | 34 | async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: 35 | """Make GET request to API.""" 36 | try: 37 | response = await self.client.get( 38 | f"{self.api_url}/{endpoint}", 39 | params=params or {} 40 | ) 41 | response.raise_for_status() 42 | return response.json() 43 | except Exception as e: 44 | logger.error(f"API request failed: {e}") 45 | raise 46 | 47 | def get_markets( 48 | self, 49 | category: Optional[str] = None, 50 | status: Optional[MarketStatus] = None, 51 | limit: int = 100 52 | ) -> List[Market]: 53 | """Get list of available markets.""" 54 | import asyncio 55 | 56 | params = {"limit": limit} 57 | if category: 58 | params["category"] = category 59 | 60 | try: 61 | data = asyncio.run(self._get("markets", params)) 62 | markets = [] 63 | 64 | for item in data.get("markets", []): 65 | market_status = MarketStatus.OPEN 66 | if item.get("closed"): 67 | market_status = MarketStatus.CLOSED 68 | if item.get("resolved"): 69 | market_status = MarketStatus.RESOLVED 70 | 71 | markets.append(Market( 72 | market_id=item.get("id", ""), 73 | question=item.get("question", ""), 74 | description=item.get("description"), 75 | outcomes=item.get("outcomes", ["YES", "NO"]), 76 | status=market_status, 77 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 78 | volume=Decimal(str(item.get("volume", 0))), 79 | liquidity=Decimal(str(item.get("liquidity", 0))), 80 | platform="polkamarkets", 81 | blockchain="polygon", 82 | metadata=item 83 | )) 84 | 85 | return markets 86 | except Exception as e: 87 | logger.error(f"Error fetching markets: {e}") 88 | return [] 89 | 90 | def get_market(self, market_id: str) -> Optional[Market]: 91 | """Get a specific market by ID.""" 92 | import asyncio 93 | 94 | try: 95 | data = asyncio.run(self._get(f"markets/{market_id}")) 96 | item = data.get("market", {}) 97 | 98 | market_status = MarketStatus.OPEN 99 | if item.get("closed"): 100 | market_status = MarketStatus.CLOSED 101 | if item.get("resolved"): 102 | market_status = MarketStatus.RESOLVED 103 | 104 | return Market( 105 | market_id=item.get("id", market_id), 106 | question=item.get("question", ""), 107 | description=item.get("description"), 108 | outcomes=item.get("outcomes", ["YES", "NO"]), 109 | status=market_status, 110 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 111 | volume=Decimal(str(item.get("volume", 0))), 112 | liquidity=Decimal(str(item.get("liquidity", 0))), 113 | platform="polkamarkets", 114 | blockchain="polygon", 115 | metadata=item 116 | ) 117 | except Exception as e: 118 | logger.error(f"Error fetching market {market_id}: {e}") 119 | return None 120 | 121 | def get_positions(self, user_address: str) -> List[Position]: 122 | """Get user's positions.""" 123 | import asyncio 124 | 125 | try: 126 | data = asyncio.run(self._get(f"users/{user_address}/positions")) 127 | positions = [] 128 | 129 | for item in data.get("positions", []): 130 | positions.append(Position( 131 | position_id=item.get("id", ""), 132 | market_id=item.get("marketId", ""), 133 | side=PositionSide.YES if item.get("side") == "YES" else PositionSide.NO, 134 | shares=Decimal(str(item.get("shares", 0))), 135 | cost_basis=Decimal(str(item.get("costBasis", 0))), 136 | current_value=Decimal(str(item.get("currentValue", 0))), 137 | platform="polkamarkets", 138 | blockchain="polygon" 139 | )) 140 | 141 | return positions 142 | except Exception as e: 143 | logger.error(f"Error fetching positions: {e}") 144 | return [] 145 | 146 | def create_position( 147 | self, 148 | market_id: str, 149 | side: PositionSide, 150 | amount: Decimal, 151 | max_price: Optional[Decimal] = None 152 | ) -> str: 153 | """Create a new position. Returns transaction hash.""" 154 | logger.warning("create_position not fully implemented - requires contract integration") 155 | raise NotImplementedError("Contract integration required") 156 | 157 | def close_position( 158 | self, 159 | position_id: str, 160 | shares: Optional[Decimal] = None 161 | ) -> str: 162 | """Close a position (full or partial). Returns transaction hash.""" 163 | logger.warning("close_position not fully implemented - requires contract integration") 164 | raise NotImplementedError("Contract integration required") 165 | 166 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 167 | """Get current price for a market side.""" 168 | import asyncio 169 | 170 | try: 171 | data = asyncio.run(self._get(f"markets/{market_id}/price")) 172 | prices = data.get("data", {}) 173 | side_str = "YES" if side == PositionSide.YES else "NO" 174 | return Decimal(str(prices.get(side_str, 0))) 175 | except Exception as e: 176 | logger.error(f"Error fetching price: {e}") 177 | return Decimal(0) 178 | 179 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 180 | """Get orderbook for a market.""" 181 | import asyncio 182 | 183 | try: 184 | data = asyncio.run(self._get(f"markets/{market_id}/orderbook")) 185 | return data.get("data", {}) 186 | except Exception as e: 187 | logger.error(f"Error fetching orderbook: {e}") 188 | return {} 189 | -------------------------------------------------------------------------------- /src/markets/hedgehog.py: -------------------------------------------------------------------------------- 1 | """Hedgehog Markets connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, List, Optional, Any 5 | from datetime import datetime 6 | from loguru import logger 7 | import httpx 8 | 9 | from ..core.interfaces import ( 10 | PredictionMarketConnector, 11 | Market, 12 | Position, 13 | PositionSide, 14 | MarketStatus 15 | ) 16 | from ..blockchains.ethereum import EthereumConnector 17 | 18 | 19 | class HedgehogMarketsConnector(PredictionMarketConnector): 20 | """Hedgehog Markets connector.""" 21 | 22 | def __init__(self, blockchain_connector: EthereumConnector, api_url: str = "https://api.hedgehog.markets"): 23 | """ 24 | Initialize Hedgehog Markets connector. 25 | 26 | Args: 27 | blockchain_connector: Ethereum blockchain connector instance 28 | api_url: Hedgehog Markets API URL 29 | """ 30 | self.blockchain = blockchain_connector 31 | self.api_url = api_url 32 | self.client = httpx.AsyncClient(timeout=30.0) 33 | 34 | async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: 35 | """Make GET request to API.""" 36 | try: 37 | response = await self.client.get( 38 | f"{self.api_url}/{endpoint}", 39 | params=params or {} 40 | ) 41 | response.raise_for_status() 42 | return response.json() 43 | except Exception as e: 44 | logger.error(f"API request failed: {e}") 45 | raise 46 | 47 | def get_markets( 48 | self, 49 | category: Optional[str] = None, 50 | status: Optional[MarketStatus] = None, 51 | limit: int = 100 52 | ) -> List[Market]: 53 | """Get list of available markets.""" 54 | import asyncio 55 | 56 | params = {"limit": limit} 57 | if category: 58 | params["category"] = category 59 | 60 | try: 61 | data = asyncio.run(self._get("markets", params)) 62 | markets = [] 63 | 64 | for item in data.get("markets", []): 65 | market_status = MarketStatus.OPEN 66 | if item.get("closed"): 67 | market_status = MarketStatus.CLOSED 68 | if item.get("resolved"): 69 | market_status = MarketStatus.RESOLVED 70 | 71 | markets.append(Market( 72 | market_id=item.get("id", ""), 73 | question=item.get("question", ""), 74 | description=item.get("description"), 75 | outcomes=item.get("outcomes", ["YES", "NO"]), 76 | status=market_status, 77 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 78 | volume=Decimal(str(item.get("volume", 0))), 79 | liquidity=Decimal(str(item.get("liquidity", 0))), 80 | platform="hedgehog", 81 | blockchain="ethereum", 82 | metadata=item 83 | )) 84 | 85 | return markets 86 | except Exception as e: 87 | logger.error(f"Error fetching markets: {e}") 88 | return [] 89 | 90 | def get_market(self, market_id: str) -> Optional[Market]: 91 | """Get a specific market by ID.""" 92 | import asyncio 93 | 94 | try: 95 | data = asyncio.run(self._get(f"markets/{market_id}")) 96 | item = data.get("market", {}) 97 | 98 | market_status = MarketStatus.OPEN 99 | if item.get("closed"): 100 | market_status = MarketStatus.CLOSED 101 | if item.get("resolved"): 102 | market_status = MarketStatus.RESOLVED 103 | 104 | return Market( 105 | market_id=item.get("id", market_id), 106 | question=item.get("question", ""), 107 | description=item.get("description"), 108 | outcomes=item.get("outcomes", ["YES", "NO"]), 109 | status=market_status, 110 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 111 | volume=Decimal(str(item.get("volume", 0))), 112 | liquidity=Decimal(str(item.get("liquidity", 0))), 113 | platform="hedgehog", 114 | blockchain="ethereum", 115 | metadata=item 116 | ) 117 | except Exception as e: 118 | logger.error(f"Error fetching market {market_id}: {e}") 119 | return None 120 | 121 | def get_positions(self, user_address: str) -> List[Position]: 122 | """Get user's positions.""" 123 | import asyncio 124 | 125 | try: 126 | data = asyncio.run(self._get(f"users/{user_address}/positions")) 127 | positions = [] 128 | 129 | for item in data.get("positions", []): 130 | positions.append(Position( 131 | position_id=item.get("id", ""), 132 | market_id=item.get("marketId", ""), 133 | side=PositionSide.YES if item.get("side") == "YES" else PositionSide.NO, 134 | shares=Decimal(str(item.get("shares", 0))), 135 | cost_basis=Decimal(str(item.get("costBasis", 0))), 136 | current_value=Decimal(str(item.get("currentValue", 0))), 137 | platform="hedgehog", 138 | blockchain="ethereum" 139 | )) 140 | 141 | return positions 142 | except Exception as e: 143 | logger.error(f"Error fetching positions: {e}") 144 | return [] 145 | 146 | def create_position( 147 | self, 148 | market_id: str, 149 | side: PositionSide, 150 | amount: Decimal, 151 | max_price: Optional[Decimal] = None 152 | ) -> str: 153 | """Create a new position. Returns transaction hash.""" 154 | logger.warning("create_position not fully implemented - requires contract integration") 155 | raise NotImplementedError("Contract integration required") 156 | 157 | def close_position( 158 | self, 159 | position_id: str, 160 | shares: Optional[Decimal] = None 161 | ) -> str: 162 | """Close a position (full or partial). Returns transaction hash.""" 163 | logger.warning("close_position not fully implemented - requires contract integration") 164 | raise NotImplementedError("Contract integration required") 165 | 166 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 167 | """Get current price for a market side.""" 168 | import asyncio 169 | 170 | try: 171 | data = asyncio.run(self._get(f"markets/{market_id}/price")) 172 | prices = data.get("data", {}) 173 | side_str = "YES" if side == PositionSide.YES else "NO" 174 | return Decimal(str(prices.get(side_str, 0))) 175 | except Exception as e: 176 | logger.error(f"Error fetching price: {e}") 177 | return Decimal(0) 178 | 179 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 180 | """Get orderbook for a market.""" 181 | import asyncio 182 | 183 | try: 184 | data = asyncio.run(self._get(f"markets/{market_id}/orderbook")) 185 | return data.get("data", {}) 186 | except Exception as e: 187 | logger.error(f"Error fetching orderbook: {e}") 188 | return {} 189 | -------------------------------------------------------------------------------- /src/markets/polymarket.py: -------------------------------------------------------------------------------- 1 | """Polymarket connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, List, Optional, Any 5 | from datetime import datetime 6 | from loguru import logger 7 | import httpx 8 | 9 | from ..core.interfaces import ( 10 | PredictionMarketConnector, 11 | Market, 12 | Position, 13 | PositionSide, 14 | MarketStatus 15 | ) 16 | from ..blockchains.polygon import PolygonConnector 17 | 18 | 19 | class PolymarketConnector(PredictionMarketConnector): 20 | """Polymarket prediction market connector.""" 21 | 22 | def __init__(self, blockchain_connector: PolygonConnector, api_url: str = "https://clob.polymarket.com"): 23 | """ 24 | Initialize Polymarket connector. 25 | 26 | Args: 27 | blockchain_connector: Polygon blockchain connector instance 28 | api_url: Polymarket API URL 29 | """ 30 | self.blockchain = blockchain_connector 31 | self.api_url = api_url 32 | self.client = httpx.AsyncClient(timeout=30.0) 33 | 34 | async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: 35 | """Make GET request to API.""" 36 | try: 37 | response = await self.client.get( 38 | f"{self.api_url}/{endpoint}", 39 | params=params or {} 40 | ) 41 | response.raise_for_status() 42 | return response.json() 43 | except Exception as e: 44 | logger.error(f"API request failed: {e}") 45 | raise 46 | 47 | def get_markets( 48 | self, 49 | category: Optional[str] = None, 50 | status: Optional[MarketStatus] = None, 51 | limit: int = 100 52 | ) -> List[Market]: 53 | """Get list of available markets.""" 54 | import asyncio 55 | 56 | params = {"limit": limit} 57 | if category: 58 | params["category"] = category 59 | 60 | try: 61 | data = asyncio.run(self._get("markets", params)) 62 | markets = [] 63 | 64 | for item in data.get("data", []): 65 | market_status = MarketStatus.OPEN 66 | if item.get("closed"): 67 | market_status = MarketStatus.CLOSED 68 | if item.get("resolved"): 69 | market_status = MarketStatus.RESOLVED 70 | 71 | markets.append(Market( 72 | market_id=item.get("id", ""), 73 | question=item.get("question", ""), 74 | description=item.get("description"), 75 | outcomes=["YES", "NO"], 76 | status=market_status, 77 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 78 | volume=Decimal(str(item.get("volume", 0))), 79 | liquidity=Decimal(str(item.get("liquidity", 0))), 80 | platform="polymarket", 81 | blockchain="polygon", 82 | metadata=item 83 | )) 84 | 85 | return markets 86 | except Exception as e: 87 | logger.error(f"Error fetching markets: {e}") 88 | return [] 89 | 90 | def get_market(self, market_id: str) -> Optional[Market]: 91 | """Get a specific market by ID.""" 92 | import asyncio 93 | 94 | try: 95 | data = asyncio.run(self._get(f"markets/{market_id}")) 96 | item = data.get("data", {}) 97 | 98 | market_status = MarketStatus.OPEN 99 | if item.get("closed"): 100 | market_status = MarketStatus.CLOSED 101 | if item.get("resolved"): 102 | market_status = MarketStatus.RESOLVED 103 | 104 | return Market( 105 | market_id=item.get("id", market_id), 106 | question=item.get("question", ""), 107 | description=item.get("description"), 108 | outcomes=["YES", "NO"], 109 | status=market_status, 110 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 111 | volume=Decimal(str(item.get("volume", 0))), 112 | liquidity=Decimal(str(item.get("liquidity", 0))), 113 | platform="polymarket", 114 | blockchain="polygon", 115 | metadata=item 116 | ) 117 | except Exception as e: 118 | logger.error(f"Error fetching market {market_id}: {e}") 119 | return None 120 | 121 | def get_positions(self, user_address: str) -> List[Position]: 122 | """Get user's positions.""" 123 | import asyncio 124 | 125 | try: 126 | data = asyncio.run(self._get(f"positions/{user_address}")) 127 | positions = [] 128 | 129 | for item in data.get("data", []): 130 | positions.append(Position( 131 | position_id=item.get("id", ""), 132 | market_id=item.get("marketId", ""), 133 | side=PositionSide.YES if item.get("side") == "YES" else PositionSide.NO, 134 | shares=Decimal(str(item.get("shares", 0))), 135 | cost_basis=Decimal(str(item.get("costBasis", 0))), 136 | current_value=Decimal(str(item.get("currentValue", 0))), 137 | platform="polymarket", 138 | blockchain="polygon" 139 | )) 140 | 141 | return positions 142 | except Exception as e: 143 | logger.error(f"Error fetching positions: {e}") 144 | return [] 145 | 146 | def create_position( 147 | self, 148 | market_id: str, 149 | side: PositionSide, 150 | amount: Decimal, 151 | max_price: Optional[Decimal] = None 152 | ) -> str: 153 | """Create a new position. Returns transaction hash.""" 154 | # This would integrate with Polymarket's smart contracts 155 | # For now, this is a placeholder that would need contract interaction 156 | logger.warning("create_position not fully implemented - requires contract integration") 157 | raise NotImplementedError("Contract integration required") 158 | 159 | def close_position( 160 | self, 161 | position_id: str, 162 | shares: Optional[Decimal] = None 163 | ) -> str: 164 | """Close a position (full or partial). Returns transaction hash.""" 165 | logger.warning("close_position not fully implemented - requires contract integration") 166 | raise NotImplementedError("Contract integration required") 167 | 168 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 169 | """Get current price for a market side.""" 170 | import asyncio 171 | 172 | try: 173 | data = asyncio.run(self._get(f"markets/{market_id}/prices")) 174 | prices = data.get("data", {}) 175 | side_str = "YES" if side == PositionSide.YES else "NO" 176 | return Decimal(str(prices.get(side_str, 0))) 177 | except Exception as e: 178 | logger.error(f"Error fetching price: {e}") 179 | return Decimal(0) 180 | 181 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 182 | """Get orderbook for a market.""" 183 | import asyncio 184 | 185 | try: 186 | data = asyncio.run(self._get(f"markets/{market_id}/orderbook")) 187 | return data.get("data", {}) 188 | except Exception as e: 189 | logger.error(f"Error fetching orderbook: {e}") 190 | return {} 191 | -------------------------------------------------------------------------------- /src/markets/myriad.py: -------------------------------------------------------------------------------- 1 | """Myriad Markets connector.""" 2 | 3 | from decimal import Decimal 4 | from typing import Dict, List, Optional, Any 5 | from datetime import datetime 6 | from loguru import logger 7 | import httpx 8 | 9 | from ..core.interfaces import ( 10 | PredictionMarketConnector, 11 | Market, 12 | Position, 13 | PositionSide, 14 | MarketStatus 15 | ) 16 | from ..blockchains.ethereum import EthereumConnector 17 | from ..blockchains.polygon import PolygonConnector 18 | 19 | 20 | class MyriadMarketsConnector(PredictionMarketConnector): 21 | """Myriad Markets connector (multi-chain).""" 22 | 23 | def __init__(self, blockchain_connector, api_url: str = "https://api.myriad.markets"): 24 | """ 25 | Initialize Myriad Markets connector. 26 | 27 | Args: 28 | blockchain_connector: Blockchain connector instance (Ethereum or Polygon) 29 | api_url: Myriad Markets API URL 30 | """ 31 | self.blockchain = blockchain_connector 32 | self.api_url = api_url 33 | self.client = httpx.AsyncClient(timeout=30.0) 34 | 35 | async def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: 36 | """Make GET request to API.""" 37 | try: 38 | response = await self.client.get( 39 | f"{self.api_url}/{endpoint}", 40 | params=params or {} 41 | ) 42 | response.raise_for_status() 43 | return response.json() 44 | except Exception as e: 45 | logger.error(f"API request failed: {e}") 46 | raise 47 | 48 | def get_markets( 49 | self, 50 | category: Optional[str] = None, 51 | status: Optional[MarketStatus] = None, 52 | limit: int = 100 53 | ) -> List[Market]: 54 | """Get list of available markets.""" 55 | import asyncio 56 | 57 | params = {"limit": limit} 58 | if category: 59 | params["category"] = category 60 | 61 | try: 62 | data = asyncio.run(self._get("markets", params)) 63 | markets = [] 64 | 65 | for item in data.get("markets", []): 66 | market_status = MarketStatus.OPEN 67 | if item.get("closed"): 68 | market_status = MarketStatus.CLOSED 69 | if item.get("resolved"): 70 | market_status = MarketStatus.RESOLVED 71 | 72 | blockchain = item.get("chain", "ethereum") 73 | markets.append(Market( 74 | market_id=item.get("id", ""), 75 | question=item.get("question", ""), 76 | description=item.get("description"), 77 | outcomes=item.get("outcomes", ["YES", "NO"]), 78 | status=market_status, 79 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 80 | volume=Decimal(str(item.get("volume", 0))), 81 | liquidity=Decimal(str(item.get("liquidity", 0))), 82 | platform="myriad", 83 | blockchain=blockchain, 84 | metadata=item 85 | )) 86 | 87 | return markets 88 | except Exception as e: 89 | logger.error(f"Error fetching markets: {e}") 90 | return [] 91 | 92 | def get_market(self, market_id: str) -> Optional[Market]: 93 | """Get a specific market by ID.""" 94 | import asyncio 95 | 96 | try: 97 | data = asyncio.run(self._get(f"markets/{market_id}")) 98 | item = data.get("market", {}) 99 | 100 | market_status = MarketStatus.OPEN 101 | if item.get("closed"): 102 | market_status = MarketStatus.CLOSED 103 | if item.get("resolved"): 104 | market_status = MarketStatus.RESOLVED 105 | 106 | blockchain = item.get("chain", "ethereum") 107 | return Market( 108 | market_id=item.get("id", market_id), 109 | question=item.get("question", ""), 110 | description=item.get("description"), 111 | outcomes=item.get("outcomes", ["YES", "NO"]), 112 | status=market_status, 113 | end_date=datetime.fromisoformat(item["endDate"]) if item.get("endDate") else None, 114 | volume=Decimal(str(item.get("volume", 0))), 115 | liquidity=Decimal(str(item.get("liquidity", 0))), 116 | platform="myriad", 117 | blockchain=blockchain, 118 | metadata=item 119 | ) 120 | except Exception as e: 121 | logger.error(f"Error fetching market {market_id}: {e}") 122 | return None 123 | 124 | def get_positions(self, user_address: str) -> List[Position]: 125 | """Get user's positions.""" 126 | import asyncio 127 | 128 | try: 129 | data = asyncio.run(self._get(f"users/{user_address}/positions")) 130 | positions = [] 131 | 132 | for item in data.get("positions", []): 133 | blockchain = item.get("chain", "ethereum") 134 | positions.append(Position( 135 | position_id=item.get("id", ""), 136 | market_id=item.get("marketId", ""), 137 | side=PositionSide.YES if item.get("side") == "YES" else PositionSide.NO, 138 | shares=Decimal(str(item.get("shares", 0))), 139 | cost_basis=Decimal(str(item.get("costBasis", 0))), 140 | current_value=Decimal(str(item.get("currentValue", 0))), 141 | platform="myriad", 142 | blockchain=blockchain 143 | )) 144 | 145 | return positions 146 | except Exception as e: 147 | logger.error(f"Error fetching positions: {e}") 148 | return [] 149 | 150 | def create_position( 151 | self, 152 | market_id: str, 153 | side: PositionSide, 154 | amount: Decimal, 155 | max_price: Optional[Decimal] = None 156 | ) -> str: 157 | """Create a new position. Returns transaction hash.""" 158 | logger.warning("create_position not fully implemented - requires contract integration") 159 | raise NotImplementedError("Contract integration required") 160 | 161 | def close_position( 162 | self, 163 | position_id: str, 164 | shares: Optional[Decimal] = None 165 | ) -> str: 166 | """Close a position (full or partial). Returns transaction hash.""" 167 | logger.warning("close_position not fully implemented - requires contract integration") 168 | raise NotImplementedError("Contract integration required") 169 | 170 | def get_price(self, market_id: str, side: PositionSide) -> Decimal: 171 | """Get current price for a market side.""" 172 | import asyncio 173 | 174 | try: 175 | data = asyncio.run(self._get(f"markets/{market_id}/price")) 176 | prices = data.get("data", {}) 177 | side_str = "YES" if side == PositionSide.YES else "NO" 178 | return Decimal(str(prices.get(side_str, 0))) 179 | except Exception as e: 180 | logger.error(f"Error fetching price: {e}") 181 | return Decimal(0) 182 | 183 | def get_orderbook(self, market_id: str) -> Dict[str, Any]: 184 | """Get orderbook for a market.""" 185 | import asyncio 186 | 187 | try: 188 | data = asyncio.run(self._get(f"markets/{market_id}/orderbook")) 189 | return data.get("data", {}) 190 | except Exception as e: 191 | logger.error(f"Error fetching orderbook: {e}") 192 | return {} 193 | -------------------------------------------------------------------------------- /src/trader/orchestrator.py: -------------------------------------------------------------------------------- 1 | """Unified trading orchestrator for all prediction markets.""" 2 | 3 | from typing import Dict, List, Optional, Any 4 | from decimal import Decimal 5 | from loguru import logger 6 | 7 | from ..core.interfaces import ( 8 | Market, 9 | Position, 10 | PositionSide, 11 | MarketStatus, 12 | PredictionMarketConnector 13 | ) 14 | from ..blockchains.ethereum import EthereumConnector 15 | from ..blockchains.polygon import PolygonConnector 16 | from ..blockchains.solana import SolanaConnector 17 | from ..blockchains.bnb_chain import BNBChainConnector 18 | from ..markets.polymarket import PolymarketConnector 19 | from ..markets.augur import AugurConnector 20 | from ..markets.moonopol import MoonopolConnector 21 | from ..markets.myriad import MyriadMarketsConnector 22 | from ..markets.drift import DriftBETConnector 23 | from ..markets.olab import OLABConnector 24 | from ..markets.polkamarkets import PolkamarketsConnector 25 | from ..markets.hedgehog import HedgehogMarketsConnector 26 | 27 | 28 | class PredictionMarketTrader: 29 | """Unified trading interface for all prediction markets.""" 30 | 31 | def __init__(self, config: Dict[str, Any]): 32 | """ 33 | Initialize the trader with configuration. 34 | 35 | Args: 36 | config: Configuration dictionary with blockchain RPCs and private keys 37 | """ 38 | self.config = config 39 | self.connectors: Dict[str, PredictionMarketConnector] = {} 40 | self._initialize_connectors() 41 | 42 | def _initialize_connectors(self): 43 | """Initialize all market connectors.""" 44 | # Initialize blockchain connectors 45 | eth_connector = None 46 | polygon_connector = None 47 | solana_connector = None 48 | bnb_connector = None 49 | 50 | # Ethereum 51 | if "ethereum" in self.config.get("blockchains", {}): 52 | eth_config = self.config["blockchains"]["ethereum"] 53 | eth_connector = EthereumConnector( 54 | rpc_url=eth_config.get("rpc_url", "https://eth.llamarpc.com"), 55 | private_key=eth_config.get("private_key") 56 | ) 57 | if eth_connector.connect(): 58 | self.connectors["augur"] = AugurConnector(eth_connector) 59 | self.connectors["hedgehog"] = HedgehogMarketsConnector(eth_connector) 60 | logger.info("Initialized Ethereum-based markets") 61 | 62 | # Polygon 63 | if "polygon" in self.config.get("blockchains", {}): 64 | polygon_config = self.config["blockchains"]["polygon"] 65 | polygon_connector = PolygonConnector( 66 | rpc_url=polygon_config.get("rpc_url"), 67 | private_key=polygon_config.get("private_key") 68 | ) 69 | if polygon_connector.connect(): 70 | self.connectors["polymarket"] = PolymarketConnector(polygon_connector) 71 | self.connectors["polkamarkets"] = PolkamarketsConnector(polygon_connector) 72 | # Myriad can use Polygon 73 | if not eth_connector: 74 | self.connectors["myriad"] = MyriadMarketsConnector(polygon_connector) 75 | logger.info("Initialized Polygon-based markets") 76 | 77 | # Solana 78 | if "solana" in self.config.get("blockchains", {}): 79 | solana_config = self.config["blockchains"]["solana"] 80 | solana_connector = SolanaConnector( 81 | rpc_url=solana_config.get("rpc_url", "https://api.mainnet-beta.solana.com"), 82 | private_key=solana_config.get("private_key") 83 | ) 84 | if solana_connector.connect(): 85 | self.connectors["moonopol"] = MoonopolConnector(solana_connector) 86 | self.connectors["drift"] = DriftBETConnector(solana_connector) 87 | logger.info("Initialized Solana-based markets") 88 | 89 | # BNB Chain 90 | if "bnb" in self.config.get("blockchains", {}): 91 | bnb_config = self.config["blockchains"]["bnb"] 92 | bnb_connector = BNBChainConnector( 93 | rpc_url=bnb_config.get("rpc_url"), 94 | private_key=bnb_config.get("private_key") 95 | ) 96 | if bnb_connector.connect(): 97 | self.connectors["olab"] = OLABConnector(bnb_connector) 98 | logger.info("Initialized BNB Chain-based markets") 99 | 100 | # Myriad (multi-chain) - prefer Ethereum if available 101 | if "myriad" not in self.connectors and eth_connector: 102 | self.connectors["myriad"] = MyriadMarketsConnector(eth_connector) 103 | 104 | logger.info(f"Initialized {len(self.connectors)} market connectors") 105 | 106 | def get_all_markets( 107 | self, 108 | platform: Optional[str] = None, 109 | category: Optional[str] = None, 110 | status: Optional[MarketStatus] = None, 111 | limit: int = 100 112 | ) -> List[Market]: 113 | """ 114 | Get markets from all or a specific platform. 115 | 116 | Args: 117 | platform: Optional platform name to filter by 118 | category: Optional category filter 119 | status: Optional status filter 120 | limit: Maximum number of markets per platform 121 | 122 | Returns: 123 | List of markets 124 | """ 125 | all_markets = [] 126 | 127 | connectors_to_query = [self.connectors[platform]] if platform and platform in self.connectors else self.connectors.values() 128 | 129 | for connector in connectors_to_query: 130 | try: 131 | markets = connector.get_markets(category=category, status=status, limit=limit) 132 | all_markets.extend(markets) 133 | logger.debug(f"Fetched {len(markets)} markets from {connector.__class__.__name__}") 134 | except Exception as e: 135 | logger.error(f"Error fetching markets from {connector.__class__.__name__}: {e}") 136 | 137 | return all_markets 138 | 139 | def get_market(self, market_id: str, platform: str) -> Optional[Market]: 140 | """ 141 | Get a specific market. 142 | 143 | Args: 144 | market_id: Market identifier 145 | platform: Platform name 146 | 147 | Returns: 148 | Market object or None 149 | """ 150 | if platform not in self.connectors: 151 | logger.error(f"Platform {platform} not available") 152 | return None 153 | 154 | try: 155 | return self.connectors[platform].get_market(market_id) 156 | except Exception as e: 157 | logger.error(f"Error fetching market {market_id} from {platform}: {e}") 158 | return None 159 | 160 | def get_all_positions(self, user_address: str, platform: Optional[str] = None) -> List[Position]: 161 | """ 162 | Get all positions across platforms. 163 | 164 | Args: 165 | user_address: User wallet address 166 | platform: Optional platform name to filter by 167 | 168 | Returns: 169 | List of positions 170 | """ 171 | all_positions = [] 172 | 173 | connectors_to_query = [self.connectors[platform]] if platform and platform in self.connectors else self.connectors.values() 174 | 175 | for connector in connectors_to_query: 176 | try: 177 | positions = connector.get_positions(user_address) 178 | all_positions.extend(positions) 179 | logger.debug(f"Fetched {len(positions)} positions from {connector.__class__.__name__}") 180 | except Exception as e: 181 | logger.error(f"Error fetching positions from {connector.__class__.__name__}: {e}") 182 | 183 | return all_positions 184 | 185 | def create_position( 186 | self, 187 | platform: str, 188 | market_id: str, 189 | side: PositionSide, 190 | amount: Decimal, 191 | max_price: Optional[Decimal] = None 192 | ) -> str: 193 | """ 194 | Create a position on a specific platform. 195 | 196 | Args: 197 | platform: Platform name 198 | market_id: Market identifier 199 | side: Position side (YES/NO) 200 | amount: Amount to invest 201 | max_price: Optional maximum price 202 | 203 | Returns: 204 | Transaction hash 205 | """ 206 | if platform not in self.connectors: 207 | raise ValueError(f"Platform {platform} not available") 208 | 209 | logger.info(f"Creating {side.value} position on {platform} market {market_id} with amount {amount}") 210 | return self.connectors[platform].create_position( 211 | market_id=market_id, 212 | side=side, 213 | amount=amount, 214 | max_price=max_price 215 | ) 216 | 217 | def close_position( 218 | self, 219 | platform: str, 220 | position_id: str, 221 | shares: Optional[Decimal] = None 222 | ) -> str: 223 | """ 224 | Close a position on a specific platform. 225 | 226 | Args: 227 | platform: Platform name 228 | position_id: Position identifier 229 | shares: Optional number of shares to close (None = close all) 230 | 231 | Returns: 232 | Transaction hash 233 | """ 234 | if platform not in self.connectors: 235 | raise ValueError(f"Platform {platform} not available") 236 | 237 | logger.info(f"Closing position {position_id} on {platform}") 238 | return self.connectors[platform].close_position( 239 | position_id=position_id, 240 | shares=shares 241 | ) 242 | 243 | def get_price(self, platform: str, market_id: str, side: PositionSide) -> Decimal: 244 | """ 245 | Get current price for a market side. 246 | 247 | Args: 248 | platform: Platform name 249 | market_id: Market identifier 250 | side: Position side (YES/NO) 251 | 252 | Returns: 253 | Current price 254 | """ 255 | if platform not in self.connectors: 256 | raise ValueError(f"Platform {platform} not available") 257 | 258 | return self.connectors[platform].get_price(market_id=market_id, side=side) 259 | 260 | def get_orderbook(self, platform: str, market_id: str) -> Dict[str, Any]: 261 | """ 262 | Get orderbook for a market. 263 | 264 | Args: 265 | platform: Platform name 266 | market_id: Market identifier 267 | 268 | Returns: 269 | Orderbook data 270 | """ 271 | if platform not in self.connectors: 272 | raise ValueError(f"Platform {platform} not available") 273 | 274 | return self.connectors[platform].get_orderbook(market_id=market_id) 275 | 276 | def list_platforms(self) -> List[str]: 277 | """Get list of available platforms.""" 278 | return list(self.connectors.keys()) 279 | --------------------------------------------------------------------------------