EnergyDecision-DT-V2-Forecast: Forecast-Conditioned Decision Transformer for AEMO FCAS Battery Trading

Model Description

EnergyDecision-DT-V2-Forecast is a ForecastDecisionTransformer β€” an extension of the modern v2 Decision Transformer that conditions on 48-step TTM price forecasts alongside the historical observation/action sequence. It predicts optimal battery dispatch and FCAS bids using both past market data and predicted future prices (RRP, demand, 4 FCAS services).

This is the first model in the benchmark to use explicit price forecasts as conditioning tokens, removing the information bottleneck of pure history conditioning.

Key Features

  • Forecast token prefix: 48 TTM-generated price forecasts prepended as a learned prefix. History tokens attend to forecast tokens via the existing causal mask.
  • RoPE enabled: Rotary Position Embeddings for better position-aware sequence modeling.
  • Modern architecture: Grouped-Query Attention (6 KV heads, 12 Q heads), QK-Norm, SwiGLU FFN, RMSNorm pre-norm, weight tying.
  • Action Space (9-dim): Energy dispatch + 8 FCAS contingency bids.
  • State Space (18-dim): Normalized market observations.
  • Context Length: 210 history + 48 forecast = 258 timesteps total.

Intended Use

This model is intended for:

  • Research into forecast-conditioned offline RL for energy markets
  • Simulation of battery trading strategies using both historical data and price forecasts
  • Baseline for comparing forecast-enhanced transformers against standard Decision Transformers and RL baselines

It is not intended for live trading without further validation, risk management, and regulatory compliance.

Training Data

  • Source: AEMO simulated trade dataset
  • Datasets used: aemo_fcas_dataset.parquet (FCAS v2, 76.9M rows) + aemo_sdp_trajectories.parquet (SDP, 2.1M rows) + aemo_fcas_grpo_dataset.parquet (GRPO, 11.9M rows)
  • Forecast conditioning: ttm_forecasts.npz β€” TTM (Granite TTM-R3) 48-step normalize price forecasts for 6 channels (RRP, demand, 4 FCAS services), aligned via episode_start column
  • Source policies: SB3(PPO, A2C, etc), GRPO-DT, SDP energy-only

Model Architecture

ForecastDecisionTransformer(
  (embed_return):      Linear(1 -> 768)
  (embed_state):       Linear(18 -> 768)
  (embed_action):      Linear(9 -> 768)
  (embed_timestep):    Embedding(100000 -> 768)
  (embed_forecast_type): Embedding(2 -> 768)  # NEW: history(0) vs forecast(1)
  (embed_ln):          RMSNorm(768)
  (transformer): 8x ModernBlock(
    (norm1): RMSNorm(768)
    (attn): CausalSelfAttention(
      q_proj: Linear(768 -> 768)   # 12 Q heads Γ— 64 head_dim
      k_proj: Linear(768 -> 384)   # 6 KV heads Γ— 64 head_dim
      v_proj: Linear(768 -> 384)   # 6 KV heads Γ— 64 head_dim
      out_proj: Linear(768 -> 768)
      qk_norm: RMSNorm(64) per head
      n_rep: 2 (GQA)
    )
    (norm2): RMSNorm(768)
    (ffn): SwiGLU(768 -> 3072 -> 768, dropout=0.15)
  )
  (ln_f): RMSNorm(768)
  (pred_act):   Linear(768 -> 9)  -> Tanh   [tied with embed_act]
  (pred_state): Linear(768 -> 18)         [tied with embed_state]
  (pred_return): Linear(768 -> 1)         [tied with embed_return]
)

Forecast token processing: At each forward pass, the model receives forecast_states (48 timesteps Γ— 18-dim) and forecast_rtgs. These are embedded with type=1 (forecast) and prepended before the history tokens. The model's causal attention allows history tokens to attend to all forecast tokens + prior history.

Hyperparameters

Parameter Value
Blocks 8
Hidden dim 768
Attention heads (Q) 12
KV heads (GQA) 6
History context length 210
Forecast length 48
Total sequence length 774 (210+48 Γ— 3 interleaved)
Dropout 0.15
QK-Norm βœ… Enabled
Weight tying βœ… Enabled
RoPE βœ… Enabled
State dim 18
Action dim 9
Discount factor 0.95
Return scale 2.0
Loss weights action=0.999, state=0.002, return=0.0001

Training Results

Loss Curves

Epoch,Train Loss,Val Loss 1,0.041705,0.007797 2,0.006725,0.005105 3,0.005220,0.004579 4,0.004722,0.004228

Epoch Train Loss Val Loss
1 0.041705 0.007797
2 0.006725 0.005105
3 0.005220 0.004579
4 0.004722 0.004228

Best validation loss: 0.004228 (epoch 4). Global steps completed: 43,648.

Training Configuration

Parameter Value
Optimizer AdamW (lr=3e-5, weight_decay=1e-4)
Batch size 64
Epochs 4
Scheduler StepLR (gamma=0.9, step=1 epoch)
Gradient clip norm 1.0
Mixed precision AMP (torch.cuda.amp)

RTG Calibration

The forecast DT uses forecast_states as conditioning, which changes the optimal RTG prompt. For the best dispatch-matched profit, calibrate RTG on your evaluation surface. Note that the forecast DT's RTG response differs from the standard DT β€” preliminary testing suggests rtg_value=0.0 remains effective, but full calibration sweeps are ongoing.

Usage

Installation

The model is loaded with the ForecastDecisionTransformer class from the energydecision repository:

git clone https://github.com/mrvictoru/energydecision.git
cd energydecision

Loading the Model

import torch
from forecast_decision_transformer import ForecastDecisionTransformer

model = ForecastDecisionTransformer(
    state_dim=18,
    act_dim=9,
    n_block=8,
    h_dim=768,
    context_len=210,
    forecast_len=48,
    n_heads=12,
    drop_p=0.15,
    max_timestep=100000,
    rope_enabled=True,
    n_kv_heads=6,
    qk_norm=True,
    tie_weights=True,
)

state_dict = torch.hub.load_state_dict_from_url(
    "https://huggingface.co/mrvictoru/energydecision-dt-v2-forecast/resolve/main/forecast_dt_model.pt",
    map_location="cpu"
)
if "model_state_dict" in state_dict:
    model.load_state_dict(state_dict["model_state_dict"])
else:
    model.load_state_dict(state_dict)
model.eval()

Inference with Forecast Tokens

import torch

# History inputs (batch, context_len, dim)
states = torch.zeros(1, 210, 18)
actions = torch.zeros(1, 210, 9)
rtgs = torch.full((1, 210, 1), 0.0)  # RTG prompt β€” use 0.0
timesteps = torch.arange(210).unsqueeze(0)
mask = torch.ones(1, 210, dtype=torch.bool)

# Forecast inputs (batch, forecast_len, dim) β€” from TTM model
forecast_states = torch.randn(1, 48, 18)  # predicted market states
forecast_rtgs = torch.randn(1, 48, 1)     # forecast RTGs
forecast_timesteps = torch.arange(210, 210 + 48).unsqueeze(0)

with torch.no_grad():
    ret, state, action = model(
        states, actions, rtgs, timesteps, mask,
        forecast_states=forecast_states,
        forecast_rtgs=forecast_rtgs,
        forecast_timesteps=forecast_timesteps,
    )
    # action[0, -1] is the next-step action (9-dim)

Using with TTM Forecasts at Inference

In a real deployment, the TTM forecasts would be generated on-the-fly from the AEMO context window:

# Forward: TTM takes last 512 steps of prices, predicts next 48
prices_context = aemo_data.iloc[current_step - 512:current_step]
ttm_forecast = ttm_model(past_values=prices_context)  # predicted prices
forecast_states = assemble_observation(ttm_forecast)   # build 18-dim from predictions

Training Infrastructure

Resource Details
Hardware CUDA-capable GPU
Framework PyTorch, transformers, granite-tsfm
Precision Mixed precision (AMP)
Checkpointing Every 500 batches + best model on validation loss
Source Code github.com/mrvictoru/energydecision

Remarks

  • Forecast quality: The TTM forecasts are generated by a fine-tuned ibm-granite/granite-timeseries-ttm-r3 model (512-48-dec-512-r3 revision). See the dataset page for details on the forecast data.
  • No perfect foresight: The model trains on the same imperfect TTM forecasts it receives at inference β€” no train-inference gap.
  • Architecture gap: Whether the forecast conditioning provides meaningful improvement over the standard DT is an open research question. The forecast DT is a step towards incorporating forward-looking information that the standard DT (210-step context window) cannot access.

Standard Tier Leaderboard (Jul 2026)

Oct 2024 Β· 5 NEM regions Β· 144h Β· medium_1c Β· full_fcas Β· best RTG per model

Model Profit/ep FCAS/ep Deg/ep Best RTG
Modern v2 $4,991 $4,836 $229 10.0
Dispatch Dalrymple North $4,660 $2,287 $1,020 β€”
Forecast DT $4,564 $3,663 $270 50.0
Phase C GRPO (mod v2) $4,322 $2,508 $1,058 10.0
Phase 1 GRPO (legacy) $2,678 $2,914 $384 50.0
PPO reference $2,353 $2,192 $236 β€”

Key findings:

  • RTG calibration (0–100) revealed every DT variant gains 5–75%. The original 0.0–2.0 range was far too narrow.
  • Modern v2 peaks at RTG=10 ($4,991/ep). Forecast DT peaks at RTG=50 ($4,564/ep).
  • The forecast DT is a well-implemented negative result β€” explicit TTM price forecasts do not beat the implicit 210-step context window.
  • FCAS TTM forecasts have near-zero correlation (~0.01–0.07), limiting any forecast-conditioned approach.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Dataset used to train mrvictoru/energydecision-dt-v2-forecast