drl-trading-bot-dev2 / src /api /portfolio_manager.py
DRL Trading Bot
Feature: HTF Agent integration β€” live trading, API endpoints, UI tab
fc115d5
Raw
History Blame Contribute Delete
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)