| 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") |
|
|
|
|
| |
|
|
| 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] |
|
|
|
|
| |
|
|
| 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}") |
|
|
| |
| 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}") |
|
|
| |
| 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}") |
|
|
| |
| 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}") |
|
|
| |
| 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')}") |
|
|
| |
| 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", "") |
|
|
| |
| 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"]) |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
|
|
| |
|
|
| 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() |
|
|
| |
| 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: |
| |
| run_cycle(use_apify=args.apify, demo_mode=args.demo) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|