esachdev12/CLINOVA
0
1import argparse2import asyncio3import json4import yaml5import time6import numpy as np7from datetime import datetime8from pathlib import Path9from tqdm import tqdm10from env.environment import FinomIQEnv11from env.agents import get_agent12from env.models import Action13from env.logging_config import setup_logger14 15logger = setup_logger("Runner")16 17 18class SimulationRunner:19 """Orchestrates automated runs for FinomIQ Hedge Fund Intelligence."""20 21 def __init__(self, config_path: str = "config.yaml"):22 with open(config_path, "r") as f:23 self.config = yaml.safe_load(f)24 25 self.env = FinomIQEnv(config_path)26 self.agent = get_agent(self.config["agent"]["type"], self.config)27 self.num_episodes = self.config["scenario"]["num_episodes"]28 self.results_path = Path(self.config["visualization"].get("persistence_path", "results/finomiq_run.json"))29 self.results_path.parent.mkdir(parents=True, exist_ok=True)30 31 async def run_episode(self, episode_idx: int) -> dict:32 """Execute a single episode simulation."""33 logger.info(f"═══════════════════════════════════════════")34 logger.info(f"EPISODE {episode_idx + 1}/{self.num_episodes} — START")35 logger.info(f"═══════════════════════════════════════════")36 37 observation = await self.env.reset()38 done = False39 episode_reward = 040 steps = 041 action_history = []42 43 while not done:44 action = self.agent.choose_action(observation)45 46 result = await self.env.step(action)47 observation = result["observation"]48 episode_reward += result["reward"]49 done = result["done"]50 steps += 151 52 action_history.append({53 "step": steps,54 "action": action.action_type,55 "asset": action.asset_name,56 "amount": action.amount,57 "reward": round(result["reward"], 4),58 "portfolio_value": observation["portfolio_value"],59 "unrealized_pnl": observation["unrealized_pnl"],60 "asset_prices": observation["asset_prices"].copy(),61 })62 63 if done:64 logger.info(f"───────────────────────────────────────────")65 logger.info(f"EPISODE {episode_idx + 1} RESULT: PnL=${observation['unrealized_pnl']:.2f} | Steps={steps}, Reward={episode_reward:.2f}")66 logger.info(f"───────────────────────────────────────────")67 68 return {69 "episode": episode_idx + 1,70 "reward": round(episode_reward, 2),71 "steps": steps,72 "final_portfolio_value": observation["portfolio_value"],73 "unrealized_pnl": observation["unrealized_pnl"],74 "action_history": action_history,75 "observation": observation,76 "history": self.env.history77 }78 return {}79 80 async def run_all(self):81 """Execute the batch of episodes and produce a rich summary."""82 start_time = time.time()83 logger.info(f"--- FinomIQ Autonomous Hedge Fund Simulation ---")84 logger.info(f"Market: {self.config['scenario']['market_type']} | Agent: {self.config['agent']['type']} | Episodes: {self.num_episodes}")85 86 results = []87 for i in tqdm(range(self.num_episodes)):88 res = await self.run_episode(i)89 results.append(res)90 91 elapsed = round(time.time() - start_time, 2)92 93 # ── Aggregate metrics ──94 rewards = [r["reward"] for r in results]95 pnls = [r["unrealized_pnl"] for r in results]96 steps_list = [r["steps"] for r in results]97 98 avg_reward = round(sum(rewards) / len(rewards) if rewards else 0, 2)99 avg_pnl = round(sum(pnls) / len(pnls) if pnls else 0, 2)100 avg_steps = round(sum(steps_list) / len(steps_list) if steps_list else 0, 1)101 102 summary = {103 "run_metadata": {104 "timestamp": datetime.now().isoformat(),105 "elapsed_seconds": elapsed,106 "market_type": self.config["scenario"]["market_type"],107 "agent_type": self.config["agent"]["type"],108 "num_episodes": self.num_episodes,109 "max_steps": self.config["scenario"]["max_steps"],110 "seed": self.config["scenario"]["seed"],111 },112 "metrics": {113 "avg_reward": avg_reward,114 "avg_pnl": avg_pnl,115 "avg_steps": avg_steps,116 "total_profit": sum(pnls),117 },118 "episodes": results,119 "config": self.config,120 }121 122 with open(self.results_path, "w") as f:123 json.dump(summary, f, indent=2)124 125 # ── Console summary ──126 logger.info(f"")127 logger.info(f"╔══════════════════════════════════════════════════════════╗")128 logger.info(f"║ FinomIQ SIMULATION SUMMARY REPORT ║")129 logger.info(f"╠══════════════════════════════════════════════════════════╣")130 logger.info(f"║ Market Regime: {self.config['scenario']['market_type']:<42}║")131 logger.info(f"║ Agent Model: {self.config['agent']['type']:<42}║")132 logger.info(f"║ Episodes: {self.num_episodes:<42}║")133 logger.info(f"║ Avg PnL: ${avg_pnl:<41}║")134 logger.info(f"║ Runtime: {elapsed}s{' ' * (40 - len(str(elapsed)))}║")135 logger.info(f"╚══════════════════════════════════════════════════════════╝")136 logger.info(f"Results saved to: {self.results_path}")137 138 139if __name__ == "__main__":140 parser = argparse.ArgumentParser()141 parser.add_argument("--config", type=str, default="config.yaml")142 args = parser.parse_args()143 144 runner = SimulationRunner(args.config)145 asyncio.run(runner.run_all())146 