energykit.cost
Translate energy data into financial impact: demand charges, imbalance costs.
energykit.cost — Translate energy data and forecast errors into dollars.
Modules
demand_charge Identify and quantify demand charge events. Simulate battery savings. imbalance Compute imbalance settlement costs from forecast errors.
- class energykit.cost.DemandChargeAnalyzer(demand_rate: float = 12.5, peak_hours: List[int] | None = None, billing_period: str = 'ME', dt_hours: float = 1.0)[source]
Bases:
objectAnalyse demand charges from power time series data.
Identifies the peak demand event in each billing period, calculates the resulting demand charge, and simulates the savings achievable with different battery peak-shaving configurations.
- Parameters:
demand_rate (float, default 12.50) – Demand charge rate in $/kW/month (applied to the highest kW in each billing period).
peak_hours (list of int or None) – Hours (0–23) during which demand charges apply. Use this for on-peak demand riders.
None= all hours (flat demand charge).billing_period (str, default
"ME") – Pandas resample frequency string for billing periods."ME"= month end,"QE"= quarter end.dt_hours (float, default 1.0) – Timestep duration in hours. Set to 0.25 for 15-minute interval data. Does not affect demand charge calculation (which is peak kW, not kWh) but is used for battery energy feasibility checks.
Examples
>>> analyzer = DemandChargeAnalyzer(demand_rate=15.0, peak_hours=list(range(9, 22))) >>> result = analyzer.analyze(power_series) >>> result.battery_savings_df
- analyze(power_kw: Series) DemandChargeResult[source]
Analyse demand charges in a power time series.
- Parameters:
power_kw (pd.Series) – Power demand in kW with a
DatetimeIndex. Sub-hourly (15-min) resolution gives the most accurate demand charge estimates.- Return type:
- class energykit.cost.DemandChargeResult(peak_events_df: DataFrame, peak_events: List[PeakEvent], total_annual_charge_usd: float, worst_event: PeakEvent, battery_savings_df: DataFrame, n_periods: int)[source]
Bases:
objectResults from
DemandChargeAnalyzer.- peak_events_df
One row per billing period with peak_kw, peak_timestamp, charge_usd.
- Type:
pd.DataFrame
- total_annual_charge_usd
Sum of all billing period demand charges over the analysed window.
- Type:
- battery_savings_df
Battery sizing → annual savings table.
- Type:
pd.DataFrame
- class energykit.cost.ImbalanceCostCalculator(imbalance_price: float = 0.08, imbalance_price_up: float | None = None, imbalance_price_down: float | None = None, dt_hours: float = 1.0)[source]
Bases:
objectCalculate imbalance settlement costs from forecast vs actual series.
Models the financial cost of forecast errors in energy markets. Uses a symmetric imbalance price by default (any deviation is penalised equally), or an asymmetric model where over- and under-forecasting have different prices.
- Parameters:
imbalance_price (float) – Symmetric imbalance price in $/kWh (applied to absolute error). Typical values: $0.05–$0.15/kWh for retail; $0.02–$0.08/kWh wholesale.
imbalance_price_up (float or None) – Price for positive imbalance (you consumed/produced MORE than forecast). If
None, usesimbalance_pricefor both directions.imbalance_price_down (float or None) – Price for negative imbalance (you consumed/produced LESS than forecast). If
None, usesimbalance_pricefor both directions.dt_hours (float, default 1.0) – Timestep duration in hours. Multiply energy error (kW × dt) → kWh.
Examples
>>> calc = ImbalanceCostCalculator(imbalance_price=0.08) >>> result = calc.compute(forecast, actual) >>> print(result)
- compute(forecast, actual, index: DatetimeIndex | None = None) ImbalanceResult[source]
Compute imbalance costs from forecast and actual series.
- Parameters:
forecast (array-like or pd.Series) – Forecasted energy / power values (kW or kWh).
actual (array-like or pd.Series) – Observed values (same units as forecast).
index (pd.DatetimeIndex or None) – Datetime index for the series. Required for temporal breakdowns. Inferred from
actualif it is apd.Series.
- Return type:
- energykit.cost.forecast_value_of_accuracy(actual, forecast, imbalance_price: float = 0.08, dt_hours: float = 1.0, target_mape_pct: float | None = None, index: DatetimeIndex | None = None) ForecastValueReport[source]
Quantify the annual financial value of improving forecast accuracy.
This function answers: “How much is each percentage point of MAPE improvement worth in dollars per year?”
The model is linear: imbalance cost ∝ MAPE. This is an approximation — the true relationship depends on the price distribution and error distribution — but it is a solid first-order estimate used in practice by trading desks for investment decisions.
- Parameters:
actual (array-like or pd.Series) – Observed values.
forecast (array-like or pd.Series) – Forecasted values.
imbalance_price (float) – Imbalance settlement price in $/kWh (default: $0.08/kWh).
dt_hours (float) – Timestep duration in hours.
target_mape_pct (float or None) – The improvement target. Defaults to half the current MAPE.
index (pd.DatetimeIndex or None) – Datetime index for annualisation.
- Returns:
Includes a full improvement table and the “break-even investment” number.
- Return type:
Examples
>>> report = forecast_value_of_accuracy(actual, forecast, imbalance_price=0.10) >>> print(report)
Demand Charge Analyzer
energykit.cost.demand_charge
Demand charge analysis and battery peak-shaving simulation.
Background
Most commercial and industrial electricity tariffs include a demand charge: a fee based on the single highest power demand in a billing period (usually the peak 15-minute or 1-hour interval in a month). This charge can represent 30–70% of a large customer’s electricity bill yet typically stems from just a handful of short events per year.
A single HVAC unit switching on at the wrong time can add $10,000 to a monthly
bill. DemandChargeAnalyzer identifies exactly when those events happened,
how much they cost, and what battery capacity would have prevented them.
Usage
>>> from energykit.cost import DemandChargeAnalyzer
>>>
>>> analyzer = DemandChargeAnalyzer(demand_rate=12.50) # $/kW/month
>>> result = analyzer.analyze(power_kw_series)
>>>
>>> print(result.peak_events_df)
>>> period peak_kw peak_timestamp demand_charge_usd
>>> 2025-01 5.23 2025-01-15 17:00:00 65.38
>>> 2025-02 4.81 2025-02-08 18:30:00 60.13
>>> ...
>>>
>>> print(result.battery_savings_df)
>>> battery_kwh max_power_kw annual_savings_usd pct_reduction
>>> 5.0 2.5 97.50 13.2
>>> 10.0 5.0 186.25 25.2
>>> 20.0 10.0 312.50 42.3
>>> 50.0 25.0 502.50 68.0
- class energykit.cost.demand_charge.DemandChargeAnalyzer(demand_rate: float = 12.5, peak_hours: List[int] | None = None, billing_period: str = 'ME', dt_hours: float = 1.0)[source]
Bases:
objectAnalyse demand charges from power time series data.
Identifies the peak demand event in each billing period, calculates the resulting demand charge, and simulates the savings achievable with different battery peak-shaving configurations.
- Parameters:
demand_rate (float, default 12.50) – Demand charge rate in $/kW/month (applied to the highest kW in each billing period).
peak_hours (list of int or None) – Hours (0–23) during which demand charges apply. Use this for on-peak demand riders.
None= all hours (flat demand charge).billing_period (str, default
"ME") – Pandas resample frequency string for billing periods."ME"= month end,"QE"= quarter end.dt_hours (float, default 1.0) – Timestep duration in hours. Set to 0.25 for 15-minute interval data. Does not affect demand charge calculation (which is peak kW, not kWh) but is used for battery energy feasibility checks.
Examples
>>> analyzer = DemandChargeAnalyzer(demand_rate=15.0, peak_hours=list(range(9, 22))) >>> result = analyzer.analyze(power_series) >>> result.battery_savings_df
- analyze(power_kw: Series) DemandChargeResult[source]
Analyse demand charges in a power time series.
- Parameters:
power_kw (pd.Series) – Power demand in kW with a
DatetimeIndex. Sub-hourly (15-min) resolution gives the most accurate demand charge estimates.- Return type:
- class energykit.cost.demand_charge.DemandChargeResult(peak_events_df: DataFrame, peak_events: List[PeakEvent], total_annual_charge_usd: float, worst_event: PeakEvent, battery_savings_df: DataFrame, n_periods: int)[source]
Bases:
objectResults from
DemandChargeAnalyzer.- peak_events_df
One row per billing period with peak_kw, peak_timestamp, charge_usd.
- Type:
pd.DataFrame
- total_annual_charge_usd
Sum of all billing period demand charges over the analysed window.
- Type:
- battery_savings_df
Battery sizing → annual savings table.
- Type:
pd.DataFrame
Imbalance Cost Calculator
energykit.cost.imbalance
Imbalance settlement cost calculation and forecast value quantification.
Background
In deregulated energy markets (EPEX-SPOT, Nord Pool, PJM, CAISO, etc.), market participants submit day-ahead schedules / bids. Deviations from the scheduled quantity in real time are settled at the imbalance price — which is almost always more expensive than the day-ahead price, creating a direct financial penalty for forecast errors.
- For a portfolio manager:
Positive imbalance (produced/consumed more than forecast): settled at a typically unfavourable price (curtailment or spill cost).
Negative imbalance (underproduced / under-consumed): settled at the imbalance price which may be 2–5× the day-ahead price during scarcity.
ImbalanceCostCalculator maps a forecast error time series directly to a
settlement cost. forecast_value_of_accuracy answers the question every
energy manager asks: “How much is it worth to improve our MAPE from 8% to 4%?”
Usage
>>> from energykit.cost import ImbalanceCostCalculator, forecast_value_of_accuracy
>>>
>>> calc = ImbalanceCostCalculator(imbalance_price=0.08) # $80/MWh → $0.08/kWh
>>> result = calc.compute(forecast=my_forecast, actual=actual_load)
>>>
>>> print(f"Imbalance cost this month: ${result.total_cost_usd:.2f}")
>>> print(result.cost_by_hour_df) # which hours of day are most expensive
>>> print(result.top_errors_df) # the 10 most costly individual errors
>>>
>>> # Value of accuracy improvement
>>> report = forecast_value_of_accuracy(actual, forecast, imbalance_price=0.08)
>>> print(report)
>>> # Current MAPE 7.2% costs $234,000/yr in imbalance settlement.
>>> # Improving to 3.6% MAPE saves $117,000/yr.
- class energykit.cost.imbalance.ForecastValueReport(current_mape_pct: float, current_annual_cost_usd: float, target_mape_pct: float, target_annual_cost_usd: float, potential_annual_savings_usd: float, cost_per_mape_pct_usd: float, breakeven_investment_usd: float, improvement_table: DataFrame)[source]
Bases:
objectOutput of
forecast_value_of_accuracy().- breakeven_investment_usd
How much a forecasting system could cost and break even in 1 year.
- Type:
- improvement_table
Columns: target_mape_pct, annual_cost_usd, annual_savings_usd.
- Type:
pd.DataFrame
- class energykit.cost.imbalance.ImbalanceCostCalculator(imbalance_price: float = 0.08, imbalance_price_up: float | None = None, imbalance_price_down: float | None = None, dt_hours: float = 1.0)[source]
Bases:
objectCalculate imbalance settlement costs from forecast vs actual series.
Models the financial cost of forecast errors in energy markets. Uses a symmetric imbalance price by default (any deviation is penalised equally), or an asymmetric model where over- and under-forecasting have different prices.
- Parameters:
imbalance_price (float) – Symmetric imbalance price in $/kWh (applied to absolute error). Typical values: $0.05–$0.15/kWh for retail; $0.02–$0.08/kWh wholesale.
imbalance_price_up (float or None) – Price for positive imbalance (you consumed/produced MORE than forecast). If
None, usesimbalance_pricefor both directions.imbalance_price_down (float or None) – Price for negative imbalance (you consumed/produced LESS than forecast). If
None, usesimbalance_pricefor both directions.dt_hours (float, default 1.0) – Timestep duration in hours. Multiply energy error (kW × dt) → kWh.
Examples
>>> calc = ImbalanceCostCalculator(imbalance_price=0.08) >>> result = calc.compute(forecast, actual) >>> print(result)
- compute(forecast, actual, index: DatetimeIndex | None = None) ImbalanceResult[source]
Compute imbalance costs from forecast and actual series.
- Parameters:
forecast (array-like or pd.Series) – Forecasted energy / power values (kW or kWh).
actual (array-like or pd.Series) – Observed values (same units as forecast).
index (pd.DatetimeIndex or None) – Datetime index for the series. Required for temporal breakdowns. Inferred from
actualif it is apd.Series.
- Return type:
- class energykit.cost.imbalance.ImbalanceResult(total_cost_usd: float, annual_cost_estimate_usd: float, cost_by_hour_df: DataFrame, cost_by_dow_df: DataFrame, cost_by_month_df: DataFrame, top_errors_df: DataFrame, current_mape_pct: float, cost_per_mape_pct_usd: float)[source]
Bases:
objectOutput of
ImbalanceCostCalculator.- cost_by_hour_df
Mean hourly imbalance cost. Reveals which times of day drive losses.
- Type:
pd.DataFrame
- cost_by_dow_df
Mean daily imbalance cost by day of week.
- Type:
pd.DataFrame
- cost_by_month_df
Monthly cost totals.
- Type:
pd.DataFrame
- top_errors_df
The 20 individual timesteps with the highest imbalance cost.
- Type:
pd.DataFrame
- cost_per_mape_pct_usd
Annual cost per 1% MAPE. Use this as the “price tag” of forecast error.
- Type:
- energykit.cost.imbalance.forecast_value_of_accuracy(actual, forecast, imbalance_price: float = 0.08, dt_hours: float = 1.0, target_mape_pct: float | None = None, index: DatetimeIndex | None = None) ForecastValueReport[source]
Quantify the annual financial value of improving forecast accuracy.
This function answers: “How much is each percentage point of MAPE improvement worth in dollars per year?”
The model is linear: imbalance cost ∝ MAPE. This is an approximation — the true relationship depends on the price distribution and error distribution — but it is a solid first-order estimate used in practice by trading desks for investment decisions.
- Parameters:
actual (array-like or pd.Series) – Observed values.
forecast (array-like or pd.Series) – Forecasted values.
imbalance_price (float) – Imbalance settlement price in $/kWh (default: $0.08/kWh).
dt_hours (float) – Timestep duration in hours.
target_mape_pct (float or None) – The improvement target. Defaults to half the current MAPE.
index (pd.DatetimeIndex or None) – Datetime index for annualisation.
- Returns:
Includes a full improvement table and the “break-even investment” number.
- Return type:
Examples
>>> report = forecast_value_of_accuracy(actual, forecast, imbalance_price=0.10) >>> print(report)