TradingAgents/tradingagents/strategy/trade_validator.py

152 lines
4.1 KiB
Python

"""
Trade Validator - Pre-trade risk checks
Validates trades before execution to ensure:
- Sufficient funds
- Position size constraints
- Price sanity checks
- Liquidity availability
"""
from typing import Optional, Dict
from datetime import datetime
class TradeValidator:
"""Validates trades before execution"""
@staticmethod
def validate_buy_order(
ticker: str,
shares: int,
price: float,
available_cash: float,
portfolio_value: float,
existing_position_value: float = 0.0,
max_position_pct: float = 0.08,
) -> Optional[Dict]:
"""
Validate a buy order.
Returns:
Dict with is_valid and any issues, or None if all good
"""
issues = []
# Check price validity
if price <= 0:
issues.append(f"Invalid price: ${price}")
# Check shares validity
if shares <= 0:
issues.append(f"Invalid shares: {shares}")
order_value = shares * price
# Check if within position limit (8% of portfolio)
new_position_value = existing_position_value + order_value
max_position_value = portfolio_value * max_position_pct
if new_position_value > max_position_value:
issues.append(
f"Position ${new_position_value:.2f} exceeds max "
f"${max_position_value:.2f} ({max_position_pct*100}% of portfolio)"
)
# Check if sufficient funds
if order_value > available_cash:
issues.append(
f"Insufficient cash: need ${order_value:.2f}, "
f"have ${available_cash:.2f}"
)
if issues:
return {
"is_valid": False,
"issues": issues,
"ticker": ticker,
"order_value": order_value,
}
return {
"is_valid": True,
"ticker": ticker,
"order_value": order_value,
"shares": shares,
"price": price,
}
@staticmethod
def validate_sell_order(
ticker: str,
shares: int,
price: float,
position_shares: int,
position_value: float,
) -> Optional[Dict]:
"""
Validate a sell order.
"""
issues = []
# Check price validity
if price <= 0:
issues.append(f"Invalid price: ${price}")
# Check shares validity
if shares <= 0:
issues.append(f"Invalid shares: {shares}")
# Check if we have enough shares
if shares > position_shares:
issues.append(
f"Trying to sell {shares} but only have {position_shares}"
)
if issues:
return {
"is_valid": False,
"issues": issues,
"ticker": ticker,
}
return {
"is_valid": True,
"ticker": ticker,
"order_value": shares * price,
"shares": shares,
"price": price,
}
@staticmethod
def validate_price_change(
ticker: str,
old_price: float,
new_price: float,
max_change_pct: float = 50.0,
) -> Dict:
"""
Validate price hasn't moved too much (circuit breaker).
Detects potential data errors or market gaps.
"""
if old_price <= 0:
return {"is_valid": True, "ticker": ticker}
change_pct = abs((new_price - old_price) / old_price) * 100
if change_pct > max_change_pct:
return {
"is_valid": False,
"ticker": ticker,
"issue": f"Extreme price change: {change_pct:.1f}%",
"old_price": old_price,
"new_price": new_price,
"change_pct": change_pct,
}
return {
"is_valid": True,
"ticker": ticker,
"change_pct": change_pct,
}