| import logging |
| import json |
| import pandas as pd |
| import numpy as np |
| import matplotlib.pyplot as plt |
| from stable_baselines3 import TD3 |
| from stable_baselines3.common.noise import NormalActionNoise |
| from rl_env import CooptimEnv |
| from src.cooptim.orchestrator import Orchestrator |
|
|
| |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s") |
| logger = logging.getLogger(__name__) |
|
|
| |
| def run_rl_inference(model, env, config): |
| """Executes a full simulation day using the trained RL model with deterministic actions.""" |
| obs, _ = env.reset() |
| done = False |
| history = [] |
| input_data = env.input_data |
| energy_col = config["columns"]["energy"] |
| fcr_col = config["columns"]["fcr"] |
| |
| step = 0 |
| while not done and step < len(input_data): |
| |
| action, _ = model.predict(obs, deterministic=True) |
| |
| current_time = input_data.index[step] |
| prices = input_data.iloc[step] |
| |
| |
| obs, reward, terminated, truncated, _ = env.step(action) |
| done = terminated or truncated |
| |
| |
| market = "Ancillary" if action[0] >= 0.6 else "Arbitrage" |
| decision = "Idle" |
| if market == "Arbitrage": |
| if action[1] > 0.1: decision = "Sell" |
| elif action[1] < -0.1: decision = "Buy" |
|
|
| history.append({ |
| "Timestamp": current_time, |
| "Market": market, |
| "Decision": decision, |
| "Power": action[1], |
| "Energy_Price": prices.get(energy_col, 0), |
| "FCR_Price": prices.get(fcr_col, 0), |
| "SoC": env.battery.soc, |
| "Revenue": reward |
| }) |
| step += 1 |
|
|
| return pd.DataFrame(history) |
|
|
| |
| def plot_rl_dashboard(df): |
| """Generates the multi-panel strategy dashboard highlighting market switches and buy/sell timing.""" |
| if df.empty: |
| logger.error("No data collected for dashboard.") |
| return |
|
|
| plt.style.use('seaborn-v0_8-muted') |
| fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(15, 12), sharex=True) |
|
|
| |
| ax1.plot(df["Timestamp"], df["SoC"], color='black', linewidth=2.5, label="Battery SoC") |
| for i in range(len(df)-1): |
| |
| color = 'palegreen' if df["Market"].iloc[i] == "Ancillary" else 'lightskyblue' |
| ax1.axvspan(df["Timestamp"].iloc[i], df["Timestamp"].iloc[i+1], color=color, alpha=0.4) |
| ax1.set_ylabel("SoC (MWh)") |
| ax1.set_title("RL Strategy: Market Selection (Green=Ancillary, Blue=Arbitrage)") |
| ax1.legend(loc='upper left') |
|
|
| |
| ax2.plot(df["Timestamp"], df["Energy_Price"], color='darkorange', label="Energy Price (€/MWh)", alpha=0.8) |
| ax2_twin = ax2.twinx() |
| |
| ax2_twin.bar(df["Timestamp"], df["Power"], width=0.008, color='purple', alpha=0.5, label="Action (+ Disch, - Ch)") |
| ax2_twin.set_ylim(-1.1, 1.1) |
| ax2.set_ylabel("Price") |
| ax2_twin.set_ylabel("Power Action") |
| ax2.set_title("Trading Decisions: Buy (Down) / Sell (Up) vs Price") |
|
|
| |
| df["CumRev"] = df["Revenue"].cumsum() |
| ax3.plot(df["Timestamp"], df["CumRev"], color='forestgreen', linewidth=2.5, label="Cumulative Profit") |
| ax3.fill_between(df["Timestamp"], df["CumRev"], color='forestgreen', alpha=0.15) |
| ax3.set_ylabel("Total Revenue (€)") |
| ax3.set_title("Real-Time Profit Accumulation") |
|
|
| plt.tight_layout() |
| plt.savefig("dashboard.png") |
| plt.show(block=True) |
|
|
| |
| if __name__ == "__main__": |
| |
| with open("config.json", "r") as f: |
| global_config = json.load(f) |
| |
| START = global_config["run"]["start_date"] |
| END = global_config["run"]["end_date"] |
|
|
| |
| logger.info(f"--- Running Baseline Co-optimization: {START} to {END} ---") |
| orchestrator = Orchestrator(config=global_config) |
| sols_coopt = orchestrator.run() |
|
|
| |
| logger.info("=== Initializing RL Multi-Market Agent Training ===") |
| try: |
| |
| training_df = sols_coopt[0].input |
| |
| |
| env = CooptimEnv(training_df, global_config) |
| |
| |
| n_actions = env.action_space.shape[0] |
| action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.3 * np.ones(n_actions)) |
|
|
| |
| model = TD3( |
| "MlpPolicy", |
| env, |
| action_noise=action_noise, |
| verbose=1, |
| learning_rate=0.0005, |
| batch_size=256 |
| ) |
| |
| |
| logger.info("Starting learning process...") |
| model.learn(total_timesteps=20000) |
| |
| |
| logger.info("=== Running Post-Training Deterministic Inference ===") |
| df_rl = run_rl_inference(model, env, global_config) |
| |
| |
| print("\n" + "="*40) |
| print(f"RL MARKET ACTIVITY SUMMARY") |
| if not df_rl.empty: |
| print(df_rl["Market"].value_counts()) |
| print("="*40) |
| |
| |
| plot_rl_dashboard(df_rl) |
| |
| |
| df_rl.to_csv("rl_trading_results.csv", index=False) |
| logger.info("Full decision report saved to rl_trading_results.csv") |
|
|
| except Exception as e: |
| logger.error(f"RL Pipeline failure: {e}") |
|
|
| logger.info("Simulation complete.") |