219 lines
7.3 KiB
Python
219 lines
7.3 KiB
Python
"""
|
|
Base Strategy Class for Polymarket Trading
|
|
|
|
All trading strategies should inherit from this class.
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Dict, Optional, List, Any
|
|
from datetime import datetime
|
|
from dataclasses import dataclass
|
|
import numpy as np
|
|
|
|
|
|
@dataclass
|
|
class MarketSignal:
|
|
"""Trading signal from strategy"""
|
|
action: str # 'BUY', 'SELL', 'HOLD'
|
|
token_id: str # Which outcome token to trade
|
|
size: float # Position size (0.0 to 1.0)
|
|
confidence: float # Confidence level (0.0 to 1.0)
|
|
reason: str # Human-readable reason
|
|
metadata: Dict[str, Any] # Additional strategy-specific data
|
|
|
|
|
|
@dataclass
|
|
class Position:
|
|
"""Open position tracking"""
|
|
token_id: str
|
|
outcome: str # 'Yes' or 'No'
|
|
size: float
|
|
entry_price: float
|
|
entry_time: datetime
|
|
current_price: float
|
|
unrealized_pnl: float
|
|
realized_pnl: float = 0.0
|
|
|
|
|
|
class BaseStrategy(ABC):
|
|
"""
|
|
Base class for all Polymarket trading strategies.
|
|
|
|
Inherit from this class and implement:
|
|
- analyze_market(): Your trading logic
|
|
- get_parameters(): Return strategy parameters
|
|
"""
|
|
|
|
def __init__(self, name: str, initial_balance: float = 1000.0):
|
|
"""
|
|
Initialize the strategy.
|
|
|
|
Args:
|
|
name: Strategy name
|
|
initial_balance: Starting USDC balance
|
|
"""
|
|
self.name = name
|
|
self.initial_balance = initial_balance
|
|
self.current_balance = initial_balance
|
|
self.equity = initial_balance
|
|
|
|
# Position tracking
|
|
self.positions: Dict[str, Position] = {} # token_id -> Position
|
|
self.closed_positions: List[Position] = []
|
|
|
|
# Performance metrics
|
|
self.total_trades = 0
|
|
self.winning_trades = 0
|
|
self.losing_trades = 0
|
|
self.total_profit = 0.0
|
|
self.total_loss = 0.0
|
|
self.max_drawdown = 0.0
|
|
self.peak_equity = initial_balance
|
|
|
|
# Risk management
|
|
self.max_position_size = 0.5 # Max 50% of balance per position
|
|
self.max_total_exposure = 0.8 # Max 80% total exposure
|
|
self.min_confidence = 0.6 # Minimum confidence to trade
|
|
|
|
@abstractmethod
|
|
def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]:
|
|
"""
|
|
Analyze market and generate trading signal.
|
|
|
|
Args:
|
|
market_data: Dictionary containing:
|
|
- 'event': Event information
|
|
- 'market': Market information
|
|
- 'prices': Current outcome prices
|
|
- 'orderbook': Orderbook data
|
|
- 'history': Historical price data (if available)
|
|
|
|
Returns:
|
|
MarketSignal or None if no trade
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_parameters(self) -> Dict[str, Any]:
|
|
"""
|
|
Return strategy parameters.
|
|
|
|
Returns:
|
|
Dictionary of parameter names and values
|
|
"""
|
|
pass
|
|
|
|
def update_position(self, token_id: str, current_price: float) -> None:
|
|
"""
|
|
Update position with current price.
|
|
|
|
Args:
|
|
token_id: Token ID
|
|
current_price: Current market price
|
|
"""
|
|
if token_id in self.positions:
|
|
pos = self.positions[token_id]
|
|
# Validate inputs
|
|
if not (np.isfinite(current_price) and current_price > 0 and current_price < 1):
|
|
return # Skip update if price is invalid
|
|
if not (np.isfinite(pos.size) and pos.size > 0):
|
|
return # Skip update if position size is invalid
|
|
if not (np.isfinite(pos.entry_price) and pos.entry_price > 0):
|
|
return # Skip update if entry price is invalid
|
|
|
|
pos.current_price = current_price
|
|
unrealized_pnl = (current_price - pos.entry_price) * pos.size
|
|
pos.unrealized_pnl = unrealized_pnl if np.isfinite(unrealized_pnl) else 0.0
|
|
|
|
def calculate_equity(self) -> float:
|
|
"""Calculate current equity (balance + unrealized PnL)"""
|
|
# Validate balance
|
|
if not np.isfinite(self.current_balance):
|
|
self.current_balance = 0.0
|
|
|
|
unrealized = sum(
|
|
pos.unrealized_pnl if np.isfinite(pos.unrealized_pnl) else 0.0
|
|
for pos in self.positions.values()
|
|
)
|
|
equity = self.current_balance + unrealized
|
|
return equity if np.isfinite(equity) else self.current_balance
|
|
|
|
def update_drawdown(self) -> None:
|
|
"""Update maximum drawdown"""
|
|
self.equity = self.calculate_equity()
|
|
if self.equity > self.peak_equity:
|
|
self.peak_equity = self.equity
|
|
|
|
# Safe division - avoid division by zero
|
|
if self.peak_equity > 0:
|
|
drawdown = (self.peak_equity - self.equity) / self.peak_equity
|
|
if drawdown > self.max_drawdown:
|
|
self.max_drawdown = drawdown
|
|
else:
|
|
# If peak_equity is 0, set drawdown to 0
|
|
self.max_drawdown = 0.0
|
|
|
|
def can_open_position(self, size: float, token_id: str) -> bool:
|
|
"""
|
|
Check if strategy can open a new position.
|
|
|
|
Args:
|
|
size: Position size in USDC
|
|
token_id: Token ID
|
|
|
|
Returns:
|
|
True if position can be opened
|
|
"""
|
|
# Validate inputs
|
|
if not (np.isfinite(size) and size > 0):
|
|
return False
|
|
if not (np.isfinite(self.current_balance) and self.current_balance > 0):
|
|
return False
|
|
|
|
# Check if already have position in this token
|
|
if token_id in self.positions:
|
|
return False
|
|
|
|
# Check position size limit
|
|
if size > self.current_balance * self.max_position_size:
|
|
return False
|
|
|
|
# Check total exposure limit
|
|
total_exposure = sum(
|
|
pos.size if np.isfinite(pos.size) else 0.0
|
|
for pos in self.positions.values()
|
|
)
|
|
if not np.isfinite(total_exposure):
|
|
total_exposure = 0.0
|
|
|
|
if total_exposure + size > self.current_balance * self.max_total_exposure:
|
|
return False
|
|
|
|
# Check balance
|
|
if size > self.current_balance:
|
|
return False
|
|
|
|
return True
|
|
|
|
def get_performance_metrics(self) -> Dict[str, Any]:
|
|
"""Get current performance metrics"""
|
|
win_rate = (self.winning_trades / self.total_trades * 100) if self.total_trades > 0 else 0.0
|
|
profit_factor = abs(self.total_profit / self.total_loss) if self.total_loss != 0 else 0.0
|
|
|
|
return {
|
|
'name': self.name,
|
|
'total_trades': self.total_trades,
|
|
'winning_trades': self.winning_trades,
|
|
'losing_trades': self.losing_trades,
|
|
'win_rate': win_rate,
|
|
'total_profit': self.total_profit,
|
|
'total_loss': self.total_loss,
|
|
'net_profit': self.total_profit + self.total_loss,
|
|
'profit_factor': profit_factor,
|
|
'max_drawdown': self.max_drawdown,
|
|
'current_balance': self.current_balance,
|
|
'equity': self.equity,
|
|
'unrealized_pnl': sum(pos.unrealized_pnl for pos in self.positions.values()),
|
|
'open_positions': len(self.positions)
|
|
}
|