import argparse import json import os import time from datetime import datetime, timezone from pathlib import Path from dotenv import load_dotenv from loguru import logger from data.weather import ( fetch_weather_openmeteo, fetch_weather_apify, get_today_high, summarize_weather, TARGET_CITIES, ) from data.polymarket import ( init_account, search_weather_markets, get_market_odds, summarize_markets, get_portfolio, get_stats, is_demo_mode, ) from agent.llm import run_agent_analysis from trading.kelly import kelly_size from trading.paper_trader import ( place_order, get_balance, get_portfolio_summary, get_stats_summary, export_trades, ) load_dotenv() RESULTS_DIR = Path("./results") RESULTS_DIR.mkdir(exist_ok=True) LOG_FILE = RESULTS_DIR / "agent.log" logger.add(LOG_FILE, rotation="10 MB", level="DEBUG") # ── Demo market ────────────────────────────────────────────────────────────── def _create_demo_market(weather_data: dict) -> list[dict]: """Create a simulated weather market for demo/testing when no real markets exist.""" city = "New York" records = weather_data.get(city, []) today_high = records[0].get("high_f", 85) if records else 85 threshold = int(today_high) + 5 yes_price = 0.30 slug = f"demo-nyc-high-above-{threshold}f" title = f"Will the high temperature in New York City exceed {threshold}°F today?" market = { "slug": slug, "title": title, "question": title, "yes_price": yes_price, "no_price": round(1 - yes_price, 3), "volume": 50000, } logger.info(f"🧪 Demo market created: [{slug}] {title}") logger.info(f" YES: {yes_price} | Today's high: {today_high}°F → threshold: {threshold}°F") return [market] # ── Main agent cycle ───────────────────────────────────────────────────────── def run_cycle(use_apify: bool = False, demo_mode: bool = False) -> dict: """ Execute one full agent cycle. Returns a summary dict. """ cycle_start = datetime.now(timezone.utc) logger.info(f"\n{'='*60}") logger.info(f"Agent cycle started: {cycle_start.isoformat()}") logger.info(f"{'='*60}") # 1. Fetch weather data logger.info("Step 1: Fetching weather data...") try: if use_apify: weather_data = fetch_weather_apify() else: weather_data = fetch_weather_openmeteo() except Exception as e: logger.warning(f"Weather fetch error ({e}), using Open-Meteo fallback") weather_data = fetch_weather_openmeteo() weather_summary = summarize_weather(weather_data) logger.info(f"\n{weather_summary}") # 2. Fetch Polymarket markets logger.info("\nStep 2: Searching Polymarket weather markets...") markets = search_weather_markets() if not markets and demo_mode: logger.info("No real markets found — injecting demo market for testing") markets = _create_demo_market(weather_data) markets_summary = summarize_markets(markets) logger.info(f"\n{markets_summary}") # 3. Get current balance & portfolio logger.info("\nStep 3: Checking portfolio...") balance = get_balance() portfolio = get_portfolio_summary() portfolio_str = json.dumps(portfolio, indent=2) if portfolio else "No open positions" logger.info(f"Balance: ${balance:,.2f}") # 4. Run LLM agent analysis logger.info("\nStep 4: Running LLM agent analysis...") agent_result = run_agent_analysis( weather_summary=weather_summary, markets_summary=markets_summary, portfolio_summary=f"Portfolio:\n{portfolio_str}", balance=balance, ) logger.info(f"Agent analysis: {agent_result.get('analysis', 'N/A')}") logger.info(f"Risk summary: {agent_result.get('risk_summary', 'N/A')}") # 5. Execute trades demo_active = is_demo_mode() logger.info("\nStep 5: Executing paper trades...") trade_results = [] executed_count = 0 for trade_signal in agent_result.get("trades", []): action = trade_signal.get("action", "skip") if action == "skip": logger.info(f" SKIP: {trade_signal.get('market_slug', '?')}") continue market_slug = trade_signal.get("market_slug", "") city = trade_signal.get("city", "") model_prob = float(trade_signal.get("model_prob", 0.5)) market_title = trade_signal.get("market_title", "") # Get current odds from paper trader (or demo market) odds = get_market_odds(market_slug) if not odds or not odds.get("yes_price"): if demo_active: odds = {"yes_price": trade_signal.get("market_prob", 0.30)} logger.info(f" Using demo odds for {market_slug}: YES={odds['yes_price']}") else: logger.warning(f" No odds for {market_slug}, skipping") continue yes_price = float(odds["yes_price"]) # Kelly sizing decision = kelly_size( model_prob=model_prob, yes_price=yes_price, bankroll=balance, ) if not decision.should_trade: logger.info(f" NO EDGE: {market_slug}") trade_results.append({ "signal": trade_signal, "decision": { "should_trade": False, "side": decision.side, "amount_usd": 0, "edge_pct": decision.edge_pct, "reasoning": decision.reasoning, }, "result": None, }) continue if demo_active: logger.info(f" ⚠️ DEMO — would {decision.side.upper()} {market_slug} for ${decision.amount_usd:.2f} ({decision.reasoning})") trade_results.append({ "signal": trade_signal, "decision": { "should_trade": True, "side": decision.side, "amount_usd": decision.amount_usd, "edge_pct": decision.edge_pct, "reasoning": f"[DEMO] {decision.reasoning}", }, "result": {"status": "demo_skipped", "reason": "Demo mode — no real trade"}, }) else: result = place_order(market_slug, decision, apply_hedge=True) trade_results.append({ "signal": trade_signal, "decision": { "should_trade": decision.should_trade, "side": decision.side, "amount_usd": decision.amount_usd, "edge_pct": decision.edge_pct, "reasoning": decision.reasoning, }, "result": result, }) executed_count += 1 # 6. Save cycle results cycle_summary = { "timestamp": cycle_start.isoformat(), "balance_before": balance, "balance_after": get_balance(), "markets_found": len(markets), "trades_signals": len(agent_result.get("trades", [])), "trades_executed": executed_count, "agent_analysis": agent_result.get("analysis"), "risk_summary": agent_result.get("risk_summary"), "trade_results": trade_results, "stats": get_stats_summary(), } out_file = RESULTS_DIR / f"cycle_{cycle_start.strftime('%Y%m%d_%H%M%S')}.json" with open(out_file, "w") as f: json.dump(cycle_summary, f, indent=2, default=str) logger.success(f"\nCycle complete. Results saved to {out_file}") logger.info( f"Summary: {executed_count} trades executed | " f"Balance: ${cycle_summary['balance_after']:,.2f}" ) return cycle_summary # ── Entry point ─────────────────────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser(description="skyAlpha — Multi-Agent Weather Prediction & Polymarket Trading System") parser.add_argument("--loop", action="store_true", help="Run continuously every 30 min") parser.add_argument("--ui", action="store_true", help="Launch Gradio dashboard") parser.add_argument("--apify", action="store_true", help="Use Apify for weather data") parser.add_argument("--interval", type=int, default=1800, help="Loop interval in seconds") parser.add_argument("--init", action="store_true", help="Initialize paper account only") parser.add_argument("--demo", action="store_true", help="Inject demo market for testing when no real markets exist") args = parser.parse_args() # Always init account on startup balance_start = float(os.getenv("STARTING_BALANCE", "10000")) init_account(balance=balance_start) if args.init: logger.info("Account initialized. Run without --init to start trading.") return if args.ui: from ui.dashboard import launch_ui launch_ui() return if args.loop: logger.info(f"Starting agent loop (interval: {args.interval}s)...") while True: try: run_cycle(use_apify=args.apify, demo_mode=args.demo) except KeyboardInterrupt: logger.info("Loop stopped by user") break except Exception as e: logger.error(f"Cycle error: {e}") logger.info(f"Sleeping {args.interval}s until next cycle...") time.sleep(args.interval) else: # Single run run_cycle(use_apify=args.apify, demo_mode=args.demo) if __name__ == "__main__": main()