File size: 6,114 Bytes
88bc772
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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

# Configuration du logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
logger = logging.getLogger(__name__)

# --- RL INFERENCE ENGINE ---
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):
        # Use deterministic=True to see the learned strategy without exploration noise
        action, _ = model.predict(obs, deterministic=True)
        
        current_time = input_data.index[step]
        prices = input_data.iloc[step]
        
        # Environment Step
        obs, reward, terminated, truncated, _ = env.step(action)
        done = terminated or truncated
        
        # Map Logic based on the 0.6 threshold defined in rl_env.py
        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)

# --- REFINED DASHBOARD ---
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)

    # 1. SoC & Market Choice (Contextual Background)
    ax1.plot(df["Timestamp"], df["SoC"], color='black', linewidth=2.5, label="Battery SoC")
    for i in range(len(df)-1):
        # Color background based on market choice
        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')

    # 2. Trading Decisions vs Energy Price
    ax2.plot(df["Timestamp"], df["Energy_Price"], color='darkorange', label="Energy Price (€/MWh)", alpha=0.8)
    ax2_twin = ax2.twinx()
    # Purple bars show the power flow (negative for charging/buying, positive for discharging/selling)
    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")

    # 3. Financial Performance
    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)

# --- MAIN BLOCK ---
if __name__ == "__main__":
    # Load configuration
    with open("config.json", "r") as f:
        global_config = json.load(f)
    
    START = global_config["run"]["start_date"]
    END = global_config["run"]["end_date"]

    # 1. Generate Baseline Data (Classical Co-optimization)
    logger.info(f"--- Running Baseline Co-optimization: {START} to {END} ---")
    orchestrator = Orchestrator(config=global_config)
    sols_coopt = orchestrator.run()

    # 2. RL Training Phase
    logger.info("=== Initializing RL Multi-Market Agent Training ===")
    try:
        # Use first day's input as training data
        training_df = sols_coopt[0].input
        
        # Setup Custom Environment
        env = CooptimEnv(training_df, global_config)
        
        # AGGRESSIVE EXPLORATION: Sigma 0.3 forces the agent to try negative power (Buying)
        n_actions = env.action_space.shape[0]
        action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.3 * np.ones(n_actions))

        # Train TD3 Model with terminal SoC penalty awareness
        model = TD3(
            "MlpPolicy", 
            env, 
            action_noise=action_noise, 
            verbose=1, 
            learning_rate=0.0005,
            batch_size=256
        )
        
        # Increased timesteps (20k) to allow agent to learn from terminal penalties
        logger.info("Starting learning process...")
        model.learn(total_timesteps=20000)
        
        # 3. Decision Dashboard & Reporting
        logger.info("=== Running Post-Training Deterministic Inference ===")
        df_rl = run_rl_inference(model, env, global_config)
        
        # Print Market Summary
        print("\n" + "="*40)
        print(f"RL MARKET ACTIVITY SUMMARY")
        if not df_rl.empty:
            print(df_rl["Market"].value_counts())
        print("="*40)
        
        # Generate the Final Dashboard
        plot_rl_dashboard(df_rl)
        
        # Save results to CSV
        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.")