function updateChartInner(g, x, y, linesGroup, color, line, data) {
- // Update axes with transitions
- x.domain(d3.extent(data, d => d.x));
- g.select(".x-axis").transition().duration(1500).call(d3.axisBottom(x).ticks(10));
- y.domain([0, d3.max(data, d => d.y)]);
- g.select(".y-axis").transition().duration(1500).call(d3.axisLeft(y).ticks(5));
-
- // Group data by basis function
- const dataByFunction = Array.from(d3.group(data, d => d.b));
- const keyFn = d => d[0];
-
- // Update basis function lines
- const u = linesGroup.selectAll("path").data(dataByFunction, keyFn);
- u.join(
- enter => enter.append("path").attr("fill","none").attr("stroke-width",3)
- .attr("stroke", (_, i) => color(i)).attr("d", d => line(d[1].map(pt => ({x: pt.x, y: 0}))))
- .style("opacity",0),
- update => update,
- exit => exit.transition().duration(1000).style("opacity",0).remove()
- )
- .transition().duration(1000)
- .attr("d", d => line(d[1]))
- .attr("stroke", (_, i) => color(i))
- .style("opacity",1);
-}
-
-chart = {
- // State variable for selected mu parameter
- let selectedMu = 1;
-
- const filteredData = () => cdf_data.filter(d =>
- Math.abs(selectedMu - d.mu) < 0.001
- );
-
- const container = d3.create("div")
- .style("max-width", "none")
- .style("width", "100%");
-
- const controlsContainer = container.append("div")
- .style("display", "flex")
- .style("gap", "20px")
- .style("align-items", "center");
-
- // Single slider control for mu
- const sliderContainer = controlsContainer.append('div')
- .style('display','flex')
- .style('align-items','center')
- .style('gap','10px')
- .style('flex','1');
-
- sliderContainer.append('label')
- .text('Naive:')
- .style('font-size','20px');
-
- const muSlider = sliderContainer.append('input')
- .attr('type','range')
- .attr('min', 0)
- .attr('max', 1)
- .attr('step', 0.1)
- .property('value', selectedMu)
- .on('input', function(event) {
- selectedMu = +this.value;
- muDisplay.text(selectedMu.toFixed(1));
- updateChart(filteredData());
- })
- .style('width', '100%')
- //.style('-webkit-appearance', 'none')
- .style('appearance', 'none')
- .style('height', '8px')
- .style('background', '#BDBDBDFF');
-
- const muDisplay = sliderContainer.append('span')
- .text(selectedMu.toFixed(1))
- .style('font-size','20px');
-
- // Add Reset button
- controlsContainer.append('button')
- .text('Reset')
- .style('font-size', '20px')
- .style('align-self', 'center')
- .style('margin-left', 'auto')
- .on('click', () => {
- selectedMu = 0.5;
- muSlider.property('value', selectedMu);
- muDisplay.text(selectedMu.toFixed(1));
- updateChart(filteredData());
- });
-
- // Build SVG
- const width = 600;
- const height = 450;
- const margin = {top: 40, right: 20, bottom: 40, left: 40};
- const innerWidth = width - margin.left - margin.right;
- const innerHeight = height - margin.top - margin.bottom;
-
- // Set controls container width to match SVG plot width
- controlsContainer.style("max-width", "none").style("width", "100%");
- // Distribute each control evenly and make sliders full-width
- controlsContainer.selectAll("div").style("flex", "1").style("min-width", "0px");
- controlsContainer.selectAll("input").style("width", "100%").style("box-sizing", "border-box");
-
- // Create scales
- const x = d3.scaleLinear()
- .range([0, innerWidth]);
-
- const y = d3.scaleLinear()
- .range([innerHeight, 0]);
-
- // Create a color scale for the basis functions
- const color = d3.scaleOrdinal(["#80C684FF", "#FFD44EFF", "#D81A5FFF"]);
-
- // Create SVG
- const svg = d3.create("svg")
- .attr("width", "100%")
- .attr("height", "auto")
- .attr("viewBox", [0, 0, width, height])
- .attr("preserveAspectRatio", "xMidYMid meet")
- .attr("style", "max-width: 100%; height: auto;");
-
- // Create the chart group
- const g = svg.append("g")
- .attr("transform", `translate(${margin.left},${margin.top})`);
-
- // Add axes
- const xAxis = g.append("g")
- .attr("transform", `translate(0,${innerHeight})`)
- .attr("class", "x-axis")
- .call(d3.axisBottom(x).ticks(10))
- .style("font-size", "20px");
-
- const yAxis = g.append("g")
- .attr("class", "y-axis")
- .call(d3.axisLeft(y).ticks(5))
- .style("font-size", "20px");
-
- // Add a horizontal line at y = 0
- g.append("line")
- .attr("x1", 0)
- .attr("x2", innerWidth)
- .attr("y1", y(0))
- .attr("y2", y(0))
- .attr("stroke", "#000")
- .attr("stroke-opacity", 0.2);
-
- // Add gridlines
- g.append("g")
- .attr("class", "grid-lines")
- .selectAll("line")
- .data(y.ticks(5))
- .join("line")
- .attr("x1", 0)
- .attr("x2", innerWidth)
- .attr("y1", d => y(d))
- .attr("y2", d => y(d))
- .attr("stroke", "#ccc")
- .attr("stroke-opacity", 0.5);
-
- // Create a line generator
- const line = d3.line()
- .x(d => x(d.x))
- .y(d => y(d.y))
- .curve(d3.curveBasis);
-
- // Group to contain the basis function lines
- const linesGroup = g.append("g")
- .attr("class", "basis-functions");
-
- // Store the current basis functions for transition
- let currentBasisFunctions = new Map();
-
- // Function to update the chart with new data
- function updateChart(data) {
- updateChartInner(g, x, y, linesGroup, color, line, data);
- }
-
- // Store the update function
- svg.node().update = updateChart;
-
- // Initial render
- updateChart(filteredData());
-
- container.node().appendChild(svg.node());
- return container.node();
-}function updateChartInner(g, x, y, linesGroup, color, line, data) {
+ // Update axes with transitions
+ x.domain(d3.extent(data, d => d.x));
+ g.select(".x-axis").transition().duration(1500).call(d3.axisBottom(x).ticks(10));
+ y.domain([0, d3.max(data, d => d.y)]);
+ g.select(".y-axis").transition().duration(1500).call(d3.axisLeft(y).ticks(5));
+
+ // Group data by basis function
+ const dataByFunction = Array.from(d3.group(data, d => d.b));
+ const keyFn = d => d[0];
+
+ // Update basis function lines
+ const u = linesGroup.selectAll("path").data(dataByFunction, keyFn);
+ u.join(
+ enter => enter.append("path").attr("fill","none").attr("stroke-width",3)
+ .attr("stroke", (_, i) => color(i)).attr("d", d => line(d[1].map(pt => ({x: pt.x, y: 0}))))
+ .style("opacity",0),
+ update => update,
+ exit => exit.transition().duration(1000).style("opacity",0).remove()
+ )
+ .transition().duration(1000)
+ .attr("d", d => line(d[1]))
+ .attr("stroke", (_, i) => color(i))
+ .style("opacity",1);
+}
+
+chart = {
+ // State variable for selected mu parameter
+ let selectedMu = 1;
+
+ const filteredData = () => cdf_data.filter(d =>
+ Math.abs(selectedMu - d.mu) < 0.001
+ );
+
+ const container = d3.create("div")
+ .style("max-width", "none")
+ .style("width", "100%");
+
+ const controlsContainer = container.append("div")
+ .style("display", "flex")
+ .style("gap", "20px")
+ .style("align-items", "center");
+
+ // Single slider control for mu
+ const sliderContainer = controlsContainer.append('div')
+ .style('display','flex')
+ .style('align-items','center')
+ .style('gap','10px')
+ .style('flex','1');
+
+ sliderContainer.append('label')
+ .text('Naive:')
+ .style('font-size','20px');
+
+ const muSlider = sliderContainer.append('input')
+ .attr('type','range')
+ .attr('min', 0)
+ .attr('max', 1)
+ .attr('step', 0.1)
+ .property('value', selectedMu)
+ .on('input', function(event) {
+ selectedMu = +this.value;
+ muDisplay.text(selectedMu.toFixed(1));
+ updateChart(filteredData());
+ })
+ .style('width', '100%')
+ //.style('-webkit-appearance', 'none')
+ .style('appearance', 'none')
+ .style('height', '8px')
+ .style('background', '#BDBDBDFF');
+
+ const muDisplay = sliderContainer.append('span')
+ .text(selectedMu.toFixed(1))
+ .style('font-size','20px');
+
+ // Add Reset button
+ controlsContainer.append('button')
+ .text('Reset')
+ .style('font-size', '20px')
+ .style('align-self', 'center')
+ .style('margin-left', 'auto')
+ .on('click', () => {
+ selectedMu = 1;
+ muSlider.property('value', selectedMu);
+ muDisplay.text(selectedMu.toFixed(1));
+ updateChart(filteredData());
+ });
+
+ // Build SVG
+ const width = 600;
+ const height = 450;
+ const margin = {top: 40, right: 20, bottom: 40, left: 40};
+ const innerWidth = width - margin.left - margin.right;
+ const innerHeight = height - margin.top - margin.bottom;
+
+ // Set controls container width to match SVG plot width
+ controlsContainer.style("max-width", "none").style("width", "100%");
+ // Distribute each control evenly and make sliders full-width
+ controlsContainer.selectAll("div").style("flex", "1").style("min-width", "0px");
+ controlsContainer.selectAll("input").style("width", "100%").style("box-sizing", "border-box");
+
+ // Create scales
+ const x = d3.scaleLinear()
+ .range([0, innerWidth]);
+
+ const y = d3.scaleLinear()
+ .range([innerHeight, 0]);
+
+ // Create a color scale for the basis functions
+ const color = d3.scaleOrdinal(["#80C684FF", "#FFD44EFF", "#D81A5FFF"]);
+
+ // Create SVG
+ const svg = d3.create("svg")
+ .attr("width", "100%")
+ .attr("height", "auto")
+ .attr("viewBox", [0, 0, width, height])
+ .attr("preserveAspectRatio", "xMidYMid meet")
+ .attr("style", "max-width: 100%; height: auto;");
+
+ // Create the chart group
+ const g = svg.append("g")
+ .attr("transform", `translate(${margin.left},${margin.top})`);
+
+ // Add axes
+ const xAxis = g.append("g")
+ .attr("transform", `translate(0,${innerHeight})`)
+ .attr("class", "x-axis")
+ .call(d3.axisBottom(x).ticks(10))
+ .style("font-size", "20px");
+
+ const yAxis = g.append("g")
+ .attr("class", "y-axis")
+ .call(d3.axisLeft(y).ticks(5))
+ .style("font-size", "20px");
+
+ // Add a horizontal line at y = 0
+ g.append("line")
+ .attr("x1", 0)
+ .attr("x2", innerWidth)
+ .attr("y1", y(0))
+ .attr("y2", y(0))
+ .attr("stroke", "#000")
+ .attr("stroke-opacity", 0.2);
+
+ // Add gridlines
+ g.append("g")
+ .attr("class", "grid-lines")
+ .selectAll("line")
+ .data(y.ticks(5))
+ .join("line")
+ .attr("x1", 0)
+ .attr("x2", innerWidth)
+ .attr("y1", d => y(d))
+ .attr("y2", d => y(d))
+ .attr("stroke", "#ccc")
+ .attr("stroke-opacity", 0.5);
+
+ // Create a line generator
+ const line = d3.line()
+ .x(d => x(d.x))
+ .y(d => y(d.y))
+ .curve(d3.curveBasis);
+
+ // Group to contain the basis function lines
+ const linesGroup = g.append("g")
+ .attr("class", "basis-functions");
+
+ // Store the current basis functions for transition
+ let currentBasisFunctions = new Map();
+
+ // Function to update the chart with new data
+ function updateChart(data) {
+ updateChartInner(g, x, y, linesGroup, color, line, data);
+ }
+
+ // Store the update function
+ svg.node().update = updateChart;
+
+ // Initial render
+ updateChart(filteredData());
+
+ container.node().appendChild(svg.node());
+ return container.node();
+}The Framework of Prediction under Expert Advice
-The sequential framework
+Each day, \(t = 1, 2, ... T\)
@@ -26276,7 +26283,7 @@ w_{t,k}^{\text{Naive}} = \frac{1}{K}\label{eq:naive_combination}\[\begin{equation*} \text{CRPS}(F, y) = \int_{\mathbb{R}} {(F(x) - \mathbb{1}\{ x > y \})}^2 dx \label{eq:crps} \end{equation*}\]
-It’s strictly proper Gneiting & Raftery (2007).
+It’s strictly proper (Gneiting & Raftery, 2007).
Using the CRPS, we can calculate time-adaptive weights \(w_{t,k}\). However, what if the experts’ performance varies in parts of the distribution?
Utilize this relation:
\[\begin{equation*} @@ -26614,7 +26621,7 @@ w_{t,k}^{\text{smooth}} = \sum_{l=1}^L \beta_l \varphi_l = \beta'\varphi
+
Multivariate Probabilistic CRPS Learning with an Application to Day-Ahead Electricity Prices
Berrisch, J., & Ziel, F. (2024). International Journal of Forecasting, 40(4), 1568-1586.
// Defined above
-// function updateChartInner(g, x, y, linesGroup, color, line, data) {
-// // Update axes with transitions
-// x.domain([0, d3.max(data, d => d.x)]);
-// g.select(".x-axis").transition().duration(1500).call(d3.axisBottom(x).ticks(10));
-// y.domain([0, d3.max(data, d => d.y)]);
-// g.select(".y-axis").transition().duration(1500).call(d3.axisLeft(y).ticks(5));
-
-// // Group data by basis function
-// const dataByFunction = Array.from(d3.group(data, d => d.b));
-// const keyFn = d => d[0];
-
-// // Update basis function lines
-// const u = linesGroup.selectAll("path").data(dataByFunction, keyFn);
-// u.join(
-// enter => enter.append("path").attr("fill","none").attr("stroke-width",3)
-// .attr("stroke", (_, i) => color(i)).attr("d", d => line(d[1].map(pt => ({x: pt.x, y: 0}))))
-// .style("opacity",0),
-// update => update,
-// exit => exit.transition().duration(1000).style("opacity",0).remove()
-// )
-// .transition().duration(1000)
-// .attr("d", d => line(d[1]))
-// .attr("stroke", (_, i) => color(i))
-// .style("opacity",1);
-// }
-
-chart0 = {
- // State variables for selected parameters
- let selectedMu = 0.5;
- let selectedSig = 1;
- let selectedNonc = 0;
- let selectedTailw = 1;
- const filteredData = () => bsplineData.filter(d =>
- Math.abs(selectedMu - d.mu) < 0.001 &&
- d.sig === selectedSig &&
- d.nonc === selectedNonc &&
- d.tailw === selectedTailw
- );
- const container = d3.create("div")
- .style("max-width", "none")
- .style("width", "100%");;
- const controlsContainer = container.append("div")
- .style("display", "flex")
- .style("gap", "20px");
- // slider controls
- const sliders = [
- { label: 'Mu', get: () => selectedMu, set: v => selectedMu = v, min: 0.1, max: 0.9, step: 0.2 },
- { label: 'Sigma', get: () => Math.log2(selectedSig), set: v => selectedSig = 2 ** v, min: -2, max: 2, step: 1 },
- { label: 'Noncentrality', get: () => selectedNonc, set: v => selectedNonc = v, min: -4, max: 4, step: 2 },
- { label: 'Tailweight', get: () => Math.log2(selectedTailw), set: v => selectedTailw = 2 ** v, min: -2, max: 2, step: 1 }
- ];
- // Build slider controls with D3 data join
- const sliderCont = controlsContainer.selectAll('div').data(sliders).join('div')
- .style('display','flex').style('align-items','center').style('gap','10px')
- .style('flex','1').style('min-width','0px');
- sliderCont.append('label').text(d => d.label + ':').style('font-size','20px');
- sliderCont.append('input')
- .attr('type','range').attr('min', d => d.min).attr('max', d => d.max).attr('step', d => d.step)
- .property('value', d => d.get())
- .on('input', function(event, d) {
- const val = +this.value; d.set(val);
- d3.select(this.parentNode).select('span').text(d.label.match(/Sigma|Tailweight/) ? 2**val : val);
- updateChart(filteredData());
- })
- .style('width', '100%');
- sliderCont.append('span').text(d => (d.label.match(/Sigma|Tailweight/) ? d.get() : d.get()))
- .style('font-size','20px');
-
- // Add Reset button to clear all sliders to their defaults
- controlsContainer.append('button')
- .text('Reset')
- .style('font-size', '20px')
- .style('align-self', 'center')
- .style('margin-left', 'auto')
- .on('click', () => {
- // reset state vars
- selectedMu = 0.5;
- selectedSig = 1;
- selectedNonc = 0;
- selectedTailw = 1;
- // update input positions
- sliderCont.selectAll('input').property('value', d => d.get());
- // update displayed labels
- sliderCont.selectAll('span')
- .text(d => d.label.match(/Sigma|Tailweight/) ? (2**d.get()) : d.get());
- // redraw chart
- updateChart(filteredData());
- });
-
- // Build SVG
- const width = 1200;
- const height = 450;
- const margin = {top: 40, right: 20, bottom: 40, left: 40};
- const innerWidth = width - margin.left - margin.right;
- const innerHeight = height - margin.top - margin.bottom;
-
- // Set controls container width to match SVG plot width
- controlsContainer.style("max-width", "none").style("width", "100%");
- // Distribute each control evenly and make sliders full-width
- controlsContainer.selectAll("div").style("flex", "1").style("min-width", "0px");
- controlsContainer.selectAll("input").style("width", "100%").style("box-sizing", "border-box");
-
- // Create scales
- const x = d3.scaleLinear()
- .domain([0, 1])
- .range([0, innerWidth]);
-
- const y = d3.scaleLinear()
- .domain([0, 1])
- .range([innerHeight, 0]);
-
- // Create a color scale for the basis functions
- const color = d3.scaleOrdinal(d3.schemeCategory10);
-
- // Create SVG
- const svg = d3.create("svg")
- .attr("width", "100%")
- .attr("height", "auto")
- .attr("viewBox", [0, 0, width, height])
- .attr("preserveAspectRatio", "xMidYMid meet")
- .attr("style", "max-width: 100%; height: auto;");
-
- // Create the chart group
- const g = svg.append("g")
- .attr("transform", `translate(${margin.left},${margin.top})`);
-
- // Add axes
- const xAxis = g.append("g")
- .attr("transform", `translate(0,${innerHeight})`)
- .attr("class", "x-axis")
- .call(d3.axisBottom(x).ticks(10))
- .style("font-size", "20px");
-
- const yAxis = g.append("g")
- .attr("class", "y-axis")
- .call(d3.axisLeft(y).ticks(5))
- .style("font-size", "20px");
-
- // Add a horizontal line at y = 0
- g.append("line")
- .attr("x1", 0)
- .attr("x2", innerWidth)
- .attr("y1", y(0))
- .attr("y2", y(0))
- .attr("stroke", "#000")
- .attr("stroke-opacity", 0.2);
-
- // Add gridlines
- g.append("g")
- .attr("class", "grid-lines")
- .selectAll("line")
- .data(y.ticks(5))
- .join("line")
- .attr("x1", 0)
- .attr("x2", innerWidth)
- .attr("y1", d => y(d))
- .attr("y2", d => y(d))
- .attr("stroke", "#ccc")
- .attr("stroke-opacity", 0.5);
-
- // Create a line generator
- const line = d3.line()
- .x(d => x(d.x))
- .y(d => y(d.y))
- .curve(d3.curveBasis);
-
- // Group to contain the basis function lines
- const linesGroup = g.append("g")
- .attr("class", "basis-functions");
-
- // Store the current basis functions for transition
- let currentBasisFunctions = new Map();
-
- // Function to update the chart with new data
- function updateChart(data) {
- updateChartInner(g, x, y, linesGroup, color, line, data);
- }
-
- // Store the update function
- svg.node().update = updateChart;
-
- // Initial render
- updateChart(filteredData());
-
- container.node().appendChild(svg.node());
- return container.node();
-}// Defined above
+// function updateChartInner(g, x, y, linesGroup, color, line, data) {
+// // Update axes with transitions
+// x.domain([0, d3.max(data, d => d.x)]);
+// g.select(".x-axis").transition().duration(1500).call(d3.axisBottom(x).ticks(10));
+// y.domain([0, d3.max(data, d => d.y)]);
+// g.select(".y-axis").transition().duration(1500).call(d3.axisLeft(y).ticks(5));
+
+// // Group data by basis function
+// const dataByFunction = Array.from(d3.group(data, d => d.b));
+// const keyFn = d => d[0];
+
+// // Update basis function lines
+// const u = linesGroup.selectAll("path").data(dataByFunction, keyFn);
+// u.join(
+// enter => enter.append("path").attr("fill","none").attr("stroke-width",3)
+// .attr("stroke", (_, i) => color(i)).attr("d", d => line(d[1].map(pt => ({x: pt.x, y: 0}))))
+// .style("opacity",0),
+// update => update,
+// exit => exit.transition().duration(1000).style("opacity",0).remove()
+// )
+// .transition().duration(1000)
+// .attr("d", d => line(d[1]))
+// .attr("stroke", (_, i) => color(i))
+// .style("opacity",1);
+// }
+
+chart0 = {
+ // State variables for selected parameters
+ let selectedMu = 0.5;
+ let selectedSig = 1;
+ let selectedNonc = 0;
+ let selectedTailw = 1;
+ const filteredData = () => bsplineData.filter(d =>
+ Math.abs(selectedMu - d.mu) < 0.001 &&
+ d.sig === selectedSig &&
+ d.nonc === selectedNonc &&
+ d.tailw === selectedTailw
+ );
+ const container = d3.create("div")
+ .style("max-width", "none")
+ .style("width", "100%");;
+ const controlsContainer = container.append("div")
+ .style("display", "flex")
+ .style("gap", "20px");
+ // slider controls
+ const sliders = [
+ { label: 'Mu', get: () => selectedMu, set: v => selectedMu = v, min: 0.1, max: 0.9, step: 0.2 },
+ { label: 'Sigma', get: () => Math.log2(selectedSig), set: v => selectedSig = 2 ** v, min: -2, max: 2, step: 1 },
+ { label: 'Noncentrality', get: () => selectedNonc, set: v => selectedNonc = v, min: -4, max: 4, step: 2 },
+ { label: 'Tailweight', get: () => Math.log2(selectedTailw), set: v => selectedTailw = 2 ** v, min: -2, max: 2, step: 1 }
+ ];
+ // Build slider controls with D3 data join
+ const sliderCont = controlsContainer.selectAll('div').data(sliders).join('div')
+ .style('display','flex').style('align-items','center').style('gap','10px')
+ .style('flex','1').style('min-width','0px');
+ sliderCont.append('label').text(d => d.label + ':').style('font-size','20px');
+ sliderCont.append('input')
+ .attr('type','range').attr('min', d => d.min).attr('max', d => d.max).attr('step', d => d.step)
+ .property('value', d => d.get())
+ .on('input', function(event, d) {
+ const val = +this.value; d.set(val);
+ d3.select(this.parentNode).select('span').text(d.label.match(/Sigma|Tailweight/) ? 2**val : val);
+ updateChart(filteredData());
+ })
+ .style('width', '100%');
+ sliderCont.append('span').text(d => (d.label.match(/Sigma|Tailweight/) ? d.get() : d.get()))
+ .style('font-size','20px');
+
+ // Add Reset button to clear all sliders to their defaults
+ controlsContainer.append('button')
+ .text('Reset')
+ .style('font-size', '20px')
+ .style('align-self', 'center')
+ .style('margin-left', 'auto')
+ .on('click', () => {
+ // reset state vars
+ selectedMu = 0.5;
+ selectedSig = 1;
+ selectedNonc = 0;
+ selectedTailw = 1;
+ // update input positions
+ sliderCont.selectAll('input').property('value', d => d.get());
+ // update displayed labels
+ sliderCont.selectAll('span')
+ .text(d => d.label.match(/Sigma|Tailweight/) ? (2**d.get()) : d.get());
+ // redraw chart
+ updateChart(filteredData());
+ });
+
+ // Build SVG
+ const width = 1200;
+ const height = 450;
+ const margin = {top: 40, right: 20, bottom: 40, left: 40};
+ const innerWidth = width - margin.left - margin.right;
+ const innerHeight = height - margin.top - margin.bottom;
+
+ // Set controls container width to match SVG plot width
+ controlsContainer.style("max-width", "none").style("width", "100%");
+ // Distribute each control evenly and make sliders full-width
+ controlsContainer.selectAll("div").style("flex", "1").style("min-width", "0px");
+ controlsContainer.selectAll("input").style("width", "100%").style("box-sizing", "border-box");
+
+ // Create scales
+ const x = d3.scaleLinear()
+ .domain([0, 1])
+ .range([0, innerWidth]);
+
+ const y = d3.scaleLinear()
+ .domain([0, 1])
+ .range([innerHeight, 0]);
+
+ // Create a color scale for the basis functions
+ const color = d3.scaleOrdinal(d3.schemeCategory10);
+
+ // Create SVG
+ const svg = d3.create("svg")
+ .attr("width", "100%")
+ .attr("height", "auto")
+ .attr("viewBox", [0, 0, width, height])
+ .attr("preserveAspectRatio", "xMidYMid meet")
+ .attr("style", "max-width: 100%; height: auto;");
+
+ // Create the chart group
+ const g = svg.append("g")
+ .attr("transform", `translate(${margin.left},${margin.top})`);
+
+ // Add axes
+ const xAxis = g.append("g")
+ .attr("transform", `translate(0,${innerHeight})`)
+ .attr("class", "x-axis")
+ .call(d3.axisBottom(x).ticks(10))
+ .style("font-size", "20px");
+
+ const yAxis = g.append("g")
+ .attr("class", "y-axis")
+ .call(d3.axisLeft(y).ticks(5))
+ .style("font-size", "20px");
+
+ // Add a horizontal line at y = 0
+ g.append("line")
+ .attr("x1", 0)
+ .attr("x2", innerWidth)
+ .attr("y1", y(0))
+ .attr("y2", y(0))
+ .attr("stroke", "#000")
+ .attr("stroke-opacity", 0.2);
+
+ // Add gridlines
+ g.append("g")
+ .attr("class", "grid-lines")
+ .selectAll("line")
+ .data(y.ticks(5))
+ .join("line")
+ .attr("x1", 0)
+ .attr("x2", innerWidth)
+ .attr("y1", d => y(d))
+ .attr("y2", d => y(d))
+ .attr("stroke", "#ccc")
+ .attr("stroke-opacity", 0.5);
+
+ // Create a line generator
+ const line = d3.line()
+ .x(d => x(d.x))
+ .y(d => y(d.y))
+ .curve(d3.curveBasis);
+
+ // Group to contain the basis function lines
+ const linesGroup = g.append("g")
+ .attr("class", "basis-functions");
+
+ // Store the current basis functions for transition
+ let currentBasisFunctions = new Map();
+
+ // Function to update the chart with new data
+ function updateChart(data) {
+ updateChartInner(g, x, y, linesGroup, color, line, data);
+ }
+
+ // Store the update function
+ svg.node().update = updateChart;
+
+ // Initial render
+ updateChart(filteredData());
+
+ container.node().appendChild(svg.node());
+ return container.node();
+}Modeling Volatility and Dependence of European Carbon and Energy Prices
Berrisch, J., Pappert, S., Ziel, F., & Arsova, A. (2023). Finance Research Letters, 52, 103503.
+
Motivation
@@ -27541,7 +27548,7 @@ Y_{t} = \mu + Y_{t-1} + \varepsilon_t \quad \text{with} \quad \varepsilon_t = \Portfolio & Risk Management,
Sustainability Planing
Political decisions
-EUA prices are obviously connected to the energy market
+EUA prices are connected to energy markets
How can the dynamics be characterized?
Several Questions arise:
-
@@ -27553,40 +27560,22 @@ Y_{t} = \mu + Y_{t-1} + \varepsilon_t \quad \text{with} \quad \varepsilon_t = \
Data
-EUA, natural gas, Brent crude oil, coal
-March 15, 2010, until October 14, 2022
-Data was normalized w.r.t. \(\text{CO}_2\) emissions
-Emission-adjusted prices reflects one tonne of \(\text{CO}_2\)
-We adjusted for inflation by Eurostat’s HICP, excluding energy
+Daily Observations: 03/15/2010 - 10/14/2022
+EUA, Natural Gas, Brent Crude Oil, Coal
+-
+
- normalized w.r.t. \(\text{CO}_2\) emissions +
- Adjusted for inflation by Eurostat’s HICP, excluding energy +
Emission-adjusted prices reflect one tonne of \(\text{CO}_2\)
Log transformation of the data to stabilize the variance
ADF Test: All series are stationary in first differences
-Johansen’s likelihood ratio trace test suggests two cointegrating relationships (levels)
-Johansen’s likelihood ratio trace test suggests no cointegrating relationships (logs)
+Johansen’s likelihood ratio trace test suggests two cointegrating relationships (only in levels)
Data
Modeling Approach: Overview
-VECM: Vector Error Correction Model
--
-
- Modeling the expectaion -
- Captures the long-run cointegrating relationship -
- Different cointegrating ranks, including rank zero (no cointegration) -
GARCH: Generalized Autoregressive Conditional Heteroscedasticity
--
-
- Captures dynamics in conditional variance -
Copula: Captures the dependence structure
--
-
- Captures: conditional cross-sectional dependencies -
- Dependence allowed to vary over time -
Modeling Approach: Notation
Sklars theorem: decompose target into - marginal distributions: \(F_{X_{k,t}|\mathcal{F}_{t-1}}\) for \(k=1,\ldots, K\), and - copula function: \(C_{\boldsymbol{U}_{t}|\mathcal{F}_{t - 1}}\)
+Sklars theorem: decompose target into
+-
+
- marginal distributions: \(F_{X_{k,t}|\mathcal{F}_{t-1}}\) for \(k=1,\ldots, K\), and +
- copula function: \(C_{\boldsymbol{U}_{t}|\mathcal{F}_{t - 1}}\) +
We take \(C\) as the \(t\)-copula
Modeling Approach: Mean and Variance
+Modeling Approach: The General Framework
Individual marginal distributions:
\[\mathbf{F} = (F_1, \ldots, F_K)^{\intercal}\]
-Generalized non-central t-distributions
+Generalized non-central t-distributions
-
-
- To account for heavy tails -
- Time varying +
- Time varying: expectation \(\boldsymbol{\mu}_t = (\mu_{1,t}, \ldots, \mu_{K,t})^{\intercal}\)
-
-
- expectation: \(\boldsymbol{\mu}_t = (\mu_{1,t}, \ldots, \mu_{K,t})^{\intercal}\)
- variance: \(\boldsymbol{\sigma}_{t}^2 = (\sigma_{1,t}^2, \ldots, \sigma_{K,t}^2)^{\intercal}\)
- Time invariant @@ -27641,46 +27632,32 @@ Y_{t} = \mu + Y_{t-1} + \varepsilon_t \quad \text{with} \quad \varepsilon_t = \
- noncentrality: \(\boldsymbol{\lambda} = (\lambda_1, \ldots, \lambda_K)^{\intercal}\)
VECM Model
\[\begin{align} \Delta \boldsymbol{\mu}_t = \Pi \boldsymbol{x}_{t-1} + \Gamma \Delta \boldsymbol{x}_{t-1} \nonumber \end{align}\]
where \(\Pi = \alpha \beta^{\intercal}\) is the cointegrating matrix of rank \(r\), \(0 \leq r\leq K\).
+GARCH model
\[\begin{align} \sigma_{i,t}^2 = & \omega_i + \alpha^+_{i} (\epsilon_{i,t-1}^+)^2 + \alpha^-_{i} (\epsilon_{i,t-1}^-)^2 + \beta_i \sigma_{i,t-1}^2 \nonumber \end{align}\]
where \(\epsilon_{i,t-1}^+ = \max\{\epsilon_{i,t-1}, 0\}\) …
-Separate coefficients for positive and negative innovations to capture leverage effects.
-Modeling Approach: Dependence
-Positive vs. negative innovations (capture leverage effects).
Time-varying dependence parameters
\[\begin{align*} \Xi_{t} = & \Lambda\left(\boldsymbol{\xi}_{t}\right) \\ \xi_{ij,t} = & \eta_{0,ij} + \eta_{1,ij} \xi_{ij,t-1} + \eta_{2,ij} z_{i,t-1} z_{j,t-1}, \end{align*}\]
-\(\xi_{ij,t}\) is a latent process
-\(z_{i,t}\) denotes the \(i\)-th standardized residual from time series \(i\) at time point \(t\)
-\(\Lambda(\cdot)\) is a link function - ensures that \(\Xi_{t}\) is a valid variance covariance matrix - ensures that \(\Xi_{t}\) does not exceed its support space and remains semi-positive definite
-Maximum Likelihood Estimation
-All parameters can be estimated jointly. Using conditional independence: \[\begin{align*} - L = f_{X_1} \prod_{i=2}^T f_{X_i|\mathcal{F}_{i-1}}, -\end{align*}\] with multivariate conditional density: \[\begin{align*} - f_{\mathbf{X}_t}(\mathbf{x}_t | \mathcal{F}_{t-1}) = c\left[\mathbf{F}(\mathbf{x}_t;\boldsymbol{\mu}_t, \boldsymbol{\sigma}_{t}^2, \boldsymbol{\nu}, - \boldsymbol{\lambda});\Xi_t, \Theta\right] \cdot \\ \prod_{i=1}^K f_{X_{i,t}}(\mathbf{x}_t;\boldsymbol{\mu}_t, \boldsymbol{\sigma}_{t}^2, \boldsymbol{\nu}, \boldsymbol{\lambda}) -\end{align*}\] The copula density \(c\) can be derived analytically.
+\(z_{i,t}\) is the \(i\)-th standardized residual from time series \(i\)
+\(\Lambda(\cdot)\) is a link function:
+-
+
- ensures that \(\Xi_{t}\) is a valid variance covariance matrix +
- ensures that \(\Xi_{t}\) does not exceed its support space and remains semi-positive definite +
- 3257 observations total
- Window size: 1000 days (~ four years) -
- Forecasting 30-steps-ahead +
- We sample 250 of 2227 starting points +
- We draw \(2^{12}= 2048\) trajectories 30 steps ahead
=> 2227 potential starting points
-We sample 250 to reduce computational cost
-We draw \(2^{12}= 2048\) trajectories from the joint predictive distribution
+Estimation
+Joint maximum lieklihood estimation:
+\[\begin{align*} + f_{\mathbf{X}_t}(\mathbf{x}_t | \mathcal{F}_{t-1}) = c\left[\mathbf{F}(\mathbf{x}_t;\boldsymbol{\mu}_t, \boldsymbol{\sigma}_{t}^2, \boldsymbol{\nu}, + \boldsymbol{\lambda});\Xi_t, \Theta\right] \cdot \\ \prod_{i=1}^K f_{X_{i,t}}(\mathbf{x}_t;\boldsymbol{\mu}_t, \boldsymbol{\sigma}_{t}^2, \boldsymbol{\nu}, \boldsymbol{\lambda}) +\end{align*}\]
+The copula density \(c\) can be derived analytically.
Evaluation
-Forecasts are evaluated by the energy score (ES)
+Our main objective is the Energy Score (ES)
\[\begin{align*} \text{ES}_t(F, \mathbf{x}_t) = \mathbb{E}_{F} \left(||\tilde{\mathbf{X}}_t - \mathbf{x}_t||_2\right) - \\ \frac{1}{2} \mathbb{E}_F \left(||\tilde{\mathbf{X}}_t - \tilde{\mathbf{X}}_t'||_2 \right) \end{align*}\]
@@ -27718,7 +27700,7 @@ Y_{t} = \mu + Y_{t-1} + \varepsilon_t \quad \text{with} \quad \varepsilon_t = \Relative improvement in ES compared to \(\text{RW}^{\sigma, \rho}\)
-Cellcolor: w.r.t. test statistic of Diebold-Mariano test (testing wether the model outperformes the benchmark, greener = better).
+Cellcolor: w.r.t. test statistic of Diebold-Mariano test (wether the model outperformes the benchmark, greener = better).
Improvement in CRPS of selected models relative to \(\textrm{RW}^{\sigma, \rho}_{}\) in % (higher = better). Colored according to the test statistic of a DM-Test comparing to \(\textrm{RW}^{\sigma, \rho}_{}\) (greener means lower test statistic i.e., better performance compared to \(\textrm{RW}^{\sigma, \rho}_{}\)).
+Relative improvement in CRPS compared to \(\text{RW}^{\sigma, \rho}\)
RMSE measures the performance of the forecasts at their mean
--
-
- Some models beat the benchmarks at short horizons -
Conclusion: the Improvements seen before must be attributed to other parts of the multivariate probabilistic predictive distribution
+Some models beat the benchmarks at short horizons
+Conclusion: the Improvements seen before must be attributed to other parts of the multivariate predictive distribution
Improvement in RMSE score of selected models relative to \(\textrm{RW}^{\sigma, \rho}_{}\) in % (higher = better). Colored according to the test statistic of a DM-Test comparing to \(\textrm{RW}^{\sigma, \rho}_{}\) (greener means lower test statistic i.e., better performance compared to \(\textrm{RW}^{\sigma, \rho}_{}\)).
+Relative improvement in RMSE compared to \(\text{RW}^{\sigma, \rho}\)
-
-
- the tails multivariate probabilistic predictive distribution +
- the tails
- the dependence structure between the marginals
Berrisch, J., Pappert, S., Ziel, F., & Arsova, A. (2023). Modeling volatility and dependence of European carbon and energy prices. Finance Research Letters, 52, 103503.
Final Remarks
+Contributions
Questions!
+ +Artwork by @allison_horst