Spaces:
Running
Running
| import json | |
| import uuid | |
| from datetime import datetime | |
| from time_utils import now_utc7 | |
| from log_utils import setup_logger | |
| from risk_manager import RiskManager | |
| from risk_manager import TradeMode, TradeProposal | |
| logger = setup_logger(__name__) | |
| class TradeExecutor: | |
| def __init__(self, risk_manager: RiskManager): | |
| self.risk = risk_manager | |
| self._pending_confirmations: dict[str, dict] = {} | |
| def prepare_trade(self, pair: str, direction: str, entry: float, | |
| sl: float, tp1: float, tp2: float = None, | |
| confidence: float = 0.5, source: str = "signal") -> dict: | |
| proposal = TradeProposal( | |
| pair=pair, direction=direction, entry=entry, | |
| sl=sl, tp1=tp1, tp2=tp2, confidence=confidence, source=source, | |
| ) | |
| result = self.risk.approve_trade(proposal) | |
| if not result["approved"]: | |
| return {"success": False, "reason": result["reason"]} | |
| trade = { | |
| "id": str(uuid.uuid4())[:8], | |
| "pair": pair, | |
| "direction": direction, | |
| "entry": entry, | |
| "sl": sl, | |
| "tp1": tp1, | |
| "tp2": tp2, | |
| "confidence": confidence, | |
| "source": source, | |
| "mode": result["mode"], | |
| "position_size": result["position_size"], | |
| "risk_amount": result["risk_amount"], | |
| "rr_ratio": result["rr_ratio"], | |
| "status": "pending" if result["mode"] == "semi_auto" else "executed", | |
| "opened_at": now_utc7().isoformat(), | |
| "closed_at": None, | |
| "pnl": None, | |
| } | |
| if result["mode"] in ("paper", "auto_controlled"): | |
| self.risk.open_position(trade) | |
| logger.info(f"Trade {trade['id']}: {direction} {pair} @ {entry} ({result['mode']})") | |
| trade["success"] = True | |
| else: | |
| self._pending_confirmations[trade["id"]] = trade | |
| trade["success"] = True | |
| trade["note"] = "Awaiting confirmation" | |
| return trade | |
| def confirm_trade(self, trade_id: str, approved: bool) -> dict: | |
| trade = self._pending_confirmations.pop(trade_id, None) | |
| if not trade: | |
| return {"success": False, "reason": "Trade not found or expired"} | |
| if not approved: | |
| logger.info(f"Trade {trade_id} rejected by user") | |
| return {"success": False, "reason": "Rejected by user"} | |
| trade["status"] = "executed" | |
| self.risk.open_position(trade) | |
| logger.info(f"Trade {trade_id} confirmed and executed") | |
| return {"success": True, "trade": trade} | |
| def close_trade(self, trade_id: str, pnl: float, reason: str = "manual"): | |
| self.risk.close_position(trade_id, pnl) | |
| logger.info(f"Trade {trade_id} closed at PnL {pnl} ({reason})") | |
| def get_pending(self) -> list[dict]: | |
| return list(self._pending_confirmations.values()) | |
| def execute_by_mode(self, signal: dict, trade_mode: str) -> dict: | |
| self.risk.set_mode(trade_mode) | |
| return self.prepare_trade( | |
| pair=signal.get("pair", ""), | |
| direction=signal.get("direction", "buy"), | |
| entry=signal.get("entry", 0), | |
| sl=signal.get("sl", 0), | |
| tp1=signal.get("tp1", 0), | |
| tp2=signal.get("tp2"), | |
| confidence=signal.get("confidence", 0.5), | |
| source=signal.get("source", "signal"), | |
| ) | |