evcc-io/templates/definition/tariff/demo-dynamic-grid.yaml

119 lines
3.9 KiB
YAML

template: demo-dynamic-grid
products:
- description:
de: Demo Börsenpreis
en: Demo Market Price
requirements:
description:
de: Zu Demonstrationszwecken. Simuliert dynamische Strompreise ähnlich EPEX Spotmarkt mit typischen Tagesmustern.
en: For demonstration purposes. Simulates dynamic electricity prices similar to EPEX spot market with typical daily patterns.
evcc: ["skiptest"]
group: price
params:
- name: min
type: pricePerKWh
default: 0.10
description:
en: Minimum price
de: Minimaler Preis
help:
en: Lowest price, typically at noon (can be negative)
de: Niedrigster Preis, typisch zur Mittagszeit (kann negativ sein)
- name: max
type: pricePerKWh
default: 0.39
description:
en: Maximum price
de: Maximaler Preis
help:
en: Highest price, typically in the evening
de: Höchster Preis, typisch am Abend
- name: noise
type: float
default: 0.01
description:
en: Price variation
de: Preisschwankung
help:
en: Random variation per slot (±value)
de: Zufällige Schwankung pro Slot (±Wert)
advanced: true
- name: interval
deprecated: true
- preset: tariff-base
render: |
type: custom
{{ include "base" . }}
forecast:
source: js
script: |
// Generate deterministic dynamic grid prices for 3 days in 15-minute slots
// Pattern repeats every 3 days for predictability
var now = new Date();
var start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
var rates = [];
var minPrice = parseFloat({{ .min }});
var maxPrice = parseFloat({{ .max }});
var priceRange = maxPrice - minPrice;
var noiseAmount = parseFloat({{ .noise }});
// Generate 15-minute slots for 3 days
var slotMinutes = 15;
var slotDuration = slotMinutes * 60 * 1000;
var slotsPerHour = 60 / slotMinutes;
var slotsPerDay = 24 * slotsPerHour;
function getDailyFactor(hour) {
if (hour < 5) {
return 0.30; // Night: 30%
} else if (hour < 7) {
var t = (hour - 5) / 2;
return 0.30 + 0.20 * t; // Morning ramp: 30% to 50%
} else if (hour < 9) {
var t = (hour - 7) / 2;
return 0.50 + 0.15 * Math.sin(Math.PI * t); // Morning peak: up to 65%
} else if (hour < 12) {
var t = (hour - 9) / 3;
return 0.50 - 0.50 * Math.pow(t, 1.5); // Drop to 0% (solar)
} else if (hour < 17) {
var t = (hour - 12) / 5;
return 0.45 * Math.pow(t, 0.8); // Recovery: 0% to 45%
} else if (hour < 20) {
var distance = Math.abs(hour - 19);
var spike = Math.exp(-Math.pow(distance / 0.8, 2));
return 0.45 + 0.55 * spike; // Evening spike: peak at 19h
} else {
var t = (hour - 20) / 4;
return 0.45 - 0.15 * t; // Decline: 45% to 30%
}
}
function getSlotNoise(day, slot) {
var totalSlot = day * slotsPerDay + slot;
var n = Math.sin(totalSlot * 12.9898) * 43758.5453;
return ((n - Math.floor(n)) * 2 - 1) * noiseAmount;
}
for (var day = 0; day < 3; day++) {
for (var slot = 0; slot < slotsPerDay; slot++) {
var time = new Date(start.getTime() + (day * slotsPerDay + slot) * slotDuration);
var hour = slot / slotsPerHour;
// Calculate base price from smooth pattern
var baseFactor = getDailyFactor(hour);
var basePrice = minPrice + priceRange * baseFactor;
// Add independent pseudo-random noise to each slot
var noise = getSlotNoise(day, slot);
var price = basePrice + noise;
rates.push({
start: time.toISOString(),
end: new Date(time.getTime() + slotDuration).toISOString(),
value: Math.round(price * 1000) / 1000 // Round to 3 decimal places
});
}
}
JSON.stringify(rates);