Local changes: Updated model training, removed debug instrumentation, and configuration improvements

This commit is contained in:
kfox
2025-12-26 01:15:43 -05:00
commit cc60da49e7
388 changed files with 57127 additions and 0 deletions

166
src/risk/limits.py Normal file
View File

@@ -0,0 +1,166 @@
"""Drawdown and loss limits."""
from decimal import Decimal
from datetime import datetime, timedelta
from typing import Dict, Any
from sqlalchemy.orm import Session
from src.core.database import get_database, PortfolioSnapshot, Trade
from src.core.config import get_config
from src.core.logger import get_logger
logger = get_logger(__name__)
class RiskLimitsManager:
"""Manages risk limits (drawdown, daily loss, etc.)."""
def __init__(self):
"""Initialize risk limits manager."""
self.db = get_database()
self.config = get_config()
self.logger = get_logger(__name__)
async def check_daily_loss_limit(self) -> bool:
"""Check if daily loss limit is exceeded."""
daily_loss_limit = Decimal(str(
self.config.get("risk.daily_loss_limit_percent", 5.0)
)) / 100
daily_pnl = await self.get_daily_pnl()
portfolio_value = await self.get_portfolio_value()
if portfolio_value == 0:
return True
daily_loss_percent = abs(daily_pnl) / portfolio_value if daily_pnl < 0 else Decimal(0)
if daily_loss_percent > daily_loss_limit:
self.logger.warning(f"Daily loss limit exceeded: {daily_loss_percent:.2%} > {daily_loss_limit:.2%}")
return False
return True
async def check_max_drawdown(self) -> bool:
"""Check if maximum drawdown limit is exceeded."""
max_drawdown_limit = Decimal(str(
self.config.get("risk.max_drawdown_percent", 20.0)
)) / 100
current_drawdown = await self.get_current_drawdown()
if current_drawdown > max_drawdown_limit:
self.logger.warning(f"Max drawdown limit exceeded: {current_drawdown:.2%} > {max_drawdown_limit:.2%}")
return False
return True
async def check_portfolio_allocation(self, symbol: str, position_value: Decimal) -> bool:
"""Check portfolio allocation limits."""
# Default: max 20% per asset
max_allocation_percent = Decimal("0.20")
portfolio_value = await self.get_portfolio_value()
if portfolio_value == 0:
return True
allocation = position_value / portfolio_value
if allocation > max_allocation_percent:
self.logger.warning(f"Portfolio allocation exceeded for {symbol}: {allocation:.2%}")
return False
return True
async def get_daily_pnl(self) -> Decimal:
"""Get today's P&L."""
from sqlalchemy import select
async with self.db.get_session() as session:
try:
today = datetime.utcnow().date()
start_of_day = datetime.combine(today, datetime.min.time())
# Get trades from today
stmt = select(Trade).where(
Trade.timestamp >= start_of_day,
Trade.paper_trading == True
)
result = await session.execute(stmt)
trades = result.scalars().all()
# Calculate P&L from trades
pnl = Decimal(0)
for trade in trades:
trade_value = trade.quantity * trade.price
fee = trade.fee if trade.fee else Decimal(0)
if trade.side.value == "sell":
pnl += trade_value - fee
else:
pnl -= trade_value + fee
return pnl
except Exception as e:
self.logger.error(f"Error calculating daily P&L: {e}")
return Decimal(0)
async def get_current_drawdown(self) -> Decimal:
"""Get current drawdown percentage."""
from sqlalchemy import select
async with self.db.get_session() as session:
try:
# Get peak portfolio value
stmt_peak = select(PortfolioSnapshot).order_by(
PortfolioSnapshot.total_value.desc()
).limit(1)
result_peak = await session.execute(stmt_peak)
peak = result_peak.scalar_one_or_none()
if not peak:
return Decimal(0)
# Get current value
stmt_current = select(PortfolioSnapshot).order_by(
PortfolioSnapshot.timestamp.desc()
).limit(1)
result_current = await session.execute(stmt_current)
current = result_current.scalar_one_or_none()
if not current or current.total_value >= peak.total_value:
return Decimal(0)
drawdown = (peak.total_value - current.total_value) / peak.total_value
return drawdown
except Exception as e:
self.logger.error(f"Error calculating drawdown: {e}")
return Decimal(0)
async def get_portfolio_value(self) -> Decimal:
"""Get current portfolio value."""
from sqlalchemy import select
async with self.db.get_session() as session:
try:
stmt = select(PortfolioSnapshot).order_by(
PortfolioSnapshot.timestamp.desc()
).limit(1)
result = await session.execute(stmt)
latest = result.scalar_one_or_none()
if latest:
return latest.total_value
return Decimal(0)
except Exception as e:
self.logger.error(f"Error getting portfolio value: {e}")
return Decimal(0)
def get_all_limits(self) -> Dict[str, Any]:
"""Get all risk limits configuration.
Returns:
Dictionary of limit configurations
"""
return {
'max_drawdown_percent': self.config.get("risk.max_drawdown_percent", 20.0),
'daily_loss_limit_percent': self.config.get("risk.daily_loss_limit_percent", 5.0),
'position_size_percent': self.config.get("risk.position_size_percent", 2.0),
}