import logging from typing import Optional import config from db.db import mark_signal_executed logger = logging.getLogger(__name__) def _get_api(): try: from alpaca_trade_api import REST except ImportError: raise ImportError("alpaca-trade-api not installed. Run: pip install alpaca-trade-api") return REST( key_id=config.ALPACA_KEY, secret_key=config.ALPACA_SECRET, base_url=config.ALPACA_BASE_URL, ) def get_portfolio_value() -> float: api = _get_api() account = api.get_account() return float(account.portfolio_value) def get_open_positions_count() -> int: api = _get_api() return len(api.list_positions()) def execute_signal(signal: dict) -> bool: if not config.ALPACA_KEY or not config.ALPACA_SECRET: logger.warning("Alpaca credentials not configured") return False ticker = signal["ticker"] try: api = _get_api() positions_count = get_open_positions_count() if positions_count >= config.MAX_POSITIONS: logger.warning(f"Max positions ({config.MAX_POSITIONS}) reached, skipping {ticker}") return False portfolio_value = get_portfolio_value() allocation = portfolio_value * config.POSITION_SIZE_PCT latest_trade = api.get_latest_trade(ticker) price = float(latest_trade.price) if price <= 0: logger.error(f"Invalid price for {ticker}: {price}") return False qty = int(allocation / price) if qty < 1: logger.warning(f"Allocation too small for {ticker}: ${allocation:.2f} at ${price:.2f}") return False existing_positions = {p.symbol: p for p in api.list_positions()} if ticker in existing_positions: existing_value = float(existing_positions[ticker].market_value) if existing_value / portfolio_value >= 0.10: logger.warning(f"Already at 10% cap for {ticker}, skipping") return False order = api.submit_order( symbol=ticker, qty=qty, side="buy", type="market", time_in_force="day", ) logger.info(f"Order submitted: {ticker} qty={qty} order_id={order.id}") mark_signal_executed(signal["id"]) return True except Exception as e: logger.error(f"Failed to execute signal for {ticker}: {e}") return False def close_position_after_days(ticker: str, holding_days: Optional[int] = None): days = holding_days or config.HOLDING_PERIOD_DAYS api = _get_api() try: api.close_position(ticker) logger.info(f"Closed position: {ticker} after {days} days") return True except Exception as e: logger.error(f"Failed to close position {ticker}: {e}") return False