| | """
|
| | Gemini 3 Flash Trading Agent — the 'Antigravity Trader'.
|
| | Analyzes XAUUSDc market data and makes autonomous trading decisions.
|
| | """
|
| |
|
| | import os
|
| | import json
|
| | from datetime import datetime
|
| | from typing import Optional
|
| | from dotenv import load_dotenv
|
| |
|
| | load_dotenv()
|
| |
|
| |
|
| | try:
|
| | from google import genai
|
| | from google.genai import types
|
| | GENAI_AVAILABLE = True
|
| | except ImportError:
|
| | GENAI_AVAILABLE = False
|
| | print("[Agent] google-genai not available — running with MOCK agent")
|
| |
|
| |
|
| | SYSTEM_PROMPT = """You are **Antigravity Trader**, an elite AI trading agent specializing in XAUUSDc (Gold vs USD) trading on MetaTrader 5.
|
| |
|
| | ## Your Role
|
| | You analyze real-time market data and make precise trading decisions. You run autonomously, making buy/sell/hold decisions based on price action, candlestick patterns, and market structure.
|
| |
|
| | ## Your Trading Rules
|
| | 1. **Risk Management First**: Never risk more than 2% of account balance per trade.
|
| | 2. **Always set SL/TP**: Stop Loss and Take Profit are mandatory. Minimum SL: 50 pips from entry. TP should be at least 1.5x the SL distance (risk-reward ratio).
|
| | 3. **One position at a time**: Don't open a new position if one is already open. You can CLOSE an existing position or HOLD.
|
| | 4. **Market Structure**: Look for support/resistance, trend direction, and key price levels in the candle data.
|
| | 5. **Confidence threshold**: Only trade with confidence >= 0.7. Below that, HOLD or DO_NOTHING.
|
| |
|
| | ## Input Data
|
| | You will receive:
|
| | - **Recent candles**: OHLCV data (most recent candles for the timeframe)
|
| | - **Current tick**: Latest bid/ask prices
|
| | - **Account info**: Balance, equity, margin, profit
|
| | - **Open positions**: Currently held positions (if any)
|
| |
|
| | ## Output Format
|
| | You MUST respond with ONLY valid JSON (no markdown, no extra text):
|
| | {
|
| | "action": "BUY" | "SELL" | "CLOSE" | "HOLD" | "DO_NOTHING",
|
| | "reasoning": "Your detailed analysis explaining WHY you made this decision. Include what patterns you see, key levels, and your risk assessment.",
|
| | "confidence": 0.0 to 1.0,
|
| | "sl": null or price level for stop loss,
|
| | "tp": null or price level for take profit,
|
| | "volume": null or lot size (e.g. 0.01)
|
| | }
|
| |
|
| | ## Action Definitions
|
| | - **BUY**: Open a long position (you expect price to go UP)
|
| | - **SELL**: Open a short position (you expect price to go DOWN)
|
| | - **CLOSE**: Close the current open position
|
| | - **HOLD**: Keep the current position open, no changes
|
| | - **DO_NOTHING**: No position open and no good setup — wait
|
| | """
|
| |
|
| |
|
| | class TradingAgent:
|
| | """Gemini 3 Flash Agent for autonomous XAUUSDc trading."""
|
| |
|
| | def __init__(self):
|
| | self.api_key = os.getenv("GEMINI_API_KEY", "")
|
| | self.model_name = "gemini-2.5-flash"
|
| | self.client = None
|
| | self.decision_history: list[dict] = []
|
| |
|
| | if GENAI_AVAILABLE and self.api_key:
|
| | self.client = genai.Client(api_key=self.api_key)
|
| | print(f"[Agent] Gemini client initialized with model: {self.model_name}")
|
| | else:
|
| | print("[Agent] No Gemini API key — using mock decisions")
|
| |
|
| | async def analyze(self, candles: list, tick: dict, account: dict,
|
| | positions: list) -> dict:
|
| | """
|
| | Analyze market data and return a trading decision.
|
| | Returns: { action, reasoning, confidence, sl, tp, volume }
|
| | """
|
| | if not self.client:
|
| |
|
| | return {
|
| | "action": "DO_NOTHING",
|
| | "reasoning": "Gemini API client not initialized (check keys)",
|
| | "confidence": 0.0,
|
| | "sl": None, "tp": None, "volume": None
|
| | }
|
| |
|
| |
|
| | recent_candles = candles[-20:] if len(candles) > 20 else candles
|
| | candle_text = self._format_candles(recent_candles)
|
| |
|
| | prompt = f"""## Current Market State — {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| |
|
| | ### Latest Tick
|
| | Bid: {tick.get('bid', 'N/A')} | Ask: {tick.get('ask', 'N/A')}
|
| |
|
| | ### Recent Candles (OHLCV, most recent last)
|
| | {candle_text}
|
| |
|
| | ### Account Status
|
| | Balance: ${account.get('balance', 0):.2f}
|
| | Equity: ${account.get('equity', 0):.2f}
|
| | Free Margin: ${account.get('free_margin', 0):.2f}
|
| | Current P&L: ${account.get('profit', 0):.2f}
|
| |
|
| | ### Open Positions
|
| | {self._format_positions(positions)}
|
| |
|
| | ### Recent Decision History
|
| | {self._format_history()}
|
| |
|
| | Analyze the market and make your trading decision now."""
|
| |
|
| | try:
|
| | response = self.client.models.generate_content(
|
| | model=self.model_name,
|
| | contents=prompt,
|
| | config=types.GenerateContentConfig(
|
| | system_instruction=SYSTEM_PROMPT,
|
| | temperature=0.3,
|
| | max_output_tokens=8192,
|
| | response_mime_type="application/json",
|
| | )
|
| | )
|
| |
|
| | response_text = response.text.strip()
|
| | decision = json.loads(response_text)
|
| |
|
| |
|
| | decision.setdefault("action", "DO_NOTHING")
|
| | decision.setdefault("reasoning", "No reasoning provided")
|
| | decision.setdefault("confidence", 0.5)
|
| | decision.setdefault("sl", None)
|
| | decision.setdefault("tp", None)
|
| | decision.setdefault("volume", float(os.getenv("DEFAULT_VOLUME", "0.01")))
|
| |
|
| |
|
| | self.decision_history.append({
|
| | "time": datetime.now().isoformat(),
|
| | "action": decision["action"],
|
| | "confidence": decision["confidence"],
|
| | })
|
| | if len(self.decision_history) > 50:
|
| | self.decision_history = self.decision_history[-50:]
|
| |
|
| | return decision
|
| |
|
| | except json.JSONDecodeError as e:
|
| | return {
|
| | "action": "DO_NOTHING",
|
| | "reasoning": f"Failed to parse agent response: {str(e)}. Raw: {response_text[:200]}",
|
| | "confidence": 0.0,
|
| | "sl": None, "tp": None, "volume": None
|
| | }
|
| | except Exception as e:
|
| | return {
|
| | "action": "DO_NOTHING",
|
| | "reasoning": f"Agent error: {str(e)}",
|
| | "confidence": 0.0,
|
| | "sl": None, "tp": None, "volume": None
|
| | }
|
| |
|
| |
|
| |
|
| | def _format_candles(self, candles: list) -> str:
|
| | """Format candle data as a readable table."""
|
| | lines = ["Time | Open | High | Low | Close | Volume"]
|
| | for c in candles:
|
| | t = datetime.fromtimestamp(c["time"]).strftime("%H:%M")
|
| | lines.append(f"{t} | {c['open']:.2f} | {c['high']:.2f} | {c['low']:.2f} | {c['close']:.2f} | {c['volume']}")
|
| | return "\n".join(lines)
|
| |
|
| | def _format_positions(self, positions: list) -> str:
|
| | if not positions:
|
| | return "No open positions."
|
| | lines = []
|
| | for p in positions:
|
| | lines.append(
|
| | f"Ticket #{p['ticket']}: {p['type'].upper()} {p['volume']} lots @ {p['price_open']:.2f} "
|
| | f"→ Current: {p['price_current']:.2f} | P&L: ${p['profit']:.2f} | SL: {p['sl']} | TP: {p['tp']}"
|
| | )
|
| | return "\n".join(lines)
|
| |
|
| | def _format_history(self) -> str:
|
| | if not self.decision_history:
|
| | return "No previous decisions."
|
| | recent = self.decision_history[-5:]
|
| | lines = []
|
| | for d in recent:
|
| | lines.append(f"{d['time']}: {d['action']} (conf: {d['confidence']:.1%})")
|
| | return "\n".join(lines)
|
| |
|