skyalpha / main.py
puravky
Initial commit: skyAlpha weather prediction & trading agent
a3e1f87
Raw
History Blame Contribute Delete
10.1 kB
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()