Files
profitable-expert-advisor/polymarket/strategies/base_strategy.py
T
2026-02-13 08:03:25 +01:00

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)
}