Download src/api/portfolio_manager.py from chen470/drl-trading-bot-dev2: direct link, hf CLI and curl.
- Browser
- Download file 3.64 kB
-
https://huggingface.co/spaces/chen470/drl-trading-bot-dev2/resolve/main/src/api/portfolio_manager.py
- Command line
-
hf download hf://spaces/chen470/drl-trading-bot-dev2/src/api/portfolio_manager.py
-
curl -L -o portfolio_manager.py https://huggingface.co/spaces/chen470/drl-trading-bot-dev2/resolve/main/src/api/portfolio_manager.py
3.64 kB
| """ | |
| Global Portfolio Manager | |
| Enforces cross-asset correlation limits and portfolio-wide risk rules. | |
| """ | |
| import logging | |
| from typing import Dict, Optional | |
| from datetime import datetime | |
| logger = logging.getLogger(__name__) | |
| class GlobalPortfolioManager: | |
| """ | |
| Manages global portfolio risk across multiple independent trading agents. | |
| Prevents correlated wipeouts by limiting concurrent directional exposure. | |
| """ | |
| def __init__(self, max_correlated_positions: int = 2): | |
| """ | |
| Initialize the global portfolio manager. | |
| Args: | |
| max_correlated_positions: Maximum number of concurrent positions | |
| allowed in the exact same direction. | |
| """ | |
| self.max_correlated_positions = max_correlated_positions | |
| # Track active positions across all agents. Format: {symbol: direction (1 or -1)} | |
| self.active_positions: Dict[str, int] = {} | |
| def register_position(self, symbol: str, direction: int): | |
| """Register that an agent successfully opened a position.""" | |
| if direction not in [1, -1]: | |
| return | |
| self.active_positions[symbol] = direction | |
| logger.info( | |
| f"π GlobalPortfolioManager: Registered {symbol} {'LONG' if direction == 1 else 'SHORT'}. " | |
| f"Active Portfolio: {self.get_portfolio_summary()}" | |
| ) | |
| def clear_position(self, symbol: str): | |
| """Clear a position when an agent exits a trade.""" | |
| if symbol in self.active_positions: | |
| direction = self.active_positions.pop(symbol) | |
| logger.info( | |
| f"π GlobalPortfolioManager: Cleared {symbol} {'LONG' if direction == 1 else 'SHORT'}. " | |
| f"Active Portfolio: {self.get_portfolio_summary()}" | |
| ) | |
| def can_open_position(self, symbol: str, proposed_direction: int) -> bool: | |
| """ | |
| Check if an agent is allowed to open a new position. | |
| Args: | |
| symbol: The asset wanting to trade (e.g. BTCUSDT) | |
| proposed_direction: 1 for LONG, -1 for SHORT | |
| Returns: | |
| True if the position is allowed under portfolio correlation limits. | |
| """ | |
| if proposed_direction not in [1, -1]: | |
| return False | |
| # Count how many existing positions are in this exact same direction | |
| correlated_count = sum( | |
| 1 for s, d in self.active_positions.items() | |
| if d == proposed_direction and s != symbol | |
| ) | |
| if correlated_count >= self.max_correlated_positions: | |
| logger.warning( | |
| f"π GlobalPortfolioManager: BLOCKED proposed {symbol} {'LONG' if proposed_direction == 1 else 'SHORT'}. " | |
| f"Correlation Limit Reached ({correlated_count}/{self.max_correlated_positions} active). " | |
| f"Active Portfolio: {self.get_portfolio_summary()}" | |
| ) | |
| return False | |
| return True | |
| def get_portfolio_summary(self) -> str: | |
| """Returns a string describing the current directional exposure.""" | |
| if not self.active_positions: | |
| return "Flat" | |
| longs = [s for s, d in self.active_positions.items() if d == 1] | |
| shorts = [s for s, d in self.active_positions.items() if d == -1] | |
| summary = [] | |
| if longs: | |
| summary.append(f"LONGs: {','.join(longs)}") | |
| if shorts: | |
| summary.append(f"SHORTs: {','.join(shorts)}") | |
| return " | ".join(summary) | |