How the Visualization Works
Three currencies define three exchange rates between them. This tool projects all three onto a single moving point in 2D space — one observation per trading period — so that their combined behaviour is readable in a single chart rather than three separate ones.
1. The problem
At any given moment, three currencies (A, B, C) produce three loop rates: A→B, B→C, C→A. Each rate changes every trading day, yielding three time series running in parallel. Plotted separately, the interactions between them are easy to miss. The goal here is to reduce all three to a single moving point in 2D without losing information — one point per time period, connected by arrows showing the direction of movement.
2. The triangular no-arbitrage condition
Define the three loop rates as:
- u = units of B per 1 unit of A
- v = units of C per 1 unit of B
- w = units of A per 1 unit of C
Converting A to B to C and back to A must return exactly 1 unit. If the product deviated from 1, a trader could run the loop indefinitely for a risk-free profit — an opportunity markets eliminate almost immediately. This gives the triangular no-arbitrage condition:
In practice, ECB reference rates deviate from 1.0 by less than 0.01%, the result of rounding and quote-timing differences. The triangular consistency indicator below the chart reports the measured mean product u×v×w.
3. Normalization
To correct any small deviation from the no-arbitrage condition symmetrically across all three currencies, the geometric mean is computed at each time point t:
Each rate is then divided by g to produce normalized rates:
After normalization, the product equals exactly 1 by construction:
The geometric mean distributes the correction equally across all three currencies — no single currency is blamed for the deviation. Normalization can be disabled in the control panel to observe its effect directly on the chart.
4. Logarithmic transformation
The natural logarithm is applied to each normalized rate:
Logarithms convert the multiplication constraint into addition:
This sum-to-zero constraint is the geometric foundation of the visualization. In three-dimensional space with axes (u'', v'', w''), the equation u'' + v'' + w'' = 0 defines a flat 2D plane through the origin. Every data point lies exactly on this plane — the data is inherently two-dimensional, just expressed using three coordinates. No information is lost in the projection.
Log-transformed rates also carry an economic interpretation: for small changes, ln(uₜ₊₁ / uₜ) approximates the percentage change in the exchange rate. A displacement of 0.05 in the chart corresponds to roughly a 5% move in relative currency values.
5. Projection into the 2D plane
The plane u'' + v'' + w'' = 0 is mapped to ordinary 2D coordinates using three basis vectors at 0°, 120°, and 240°. The symmetric arrangement means no currency has a preferred axis:
Each data point is projected by combining its three log-transformed coordinates with the three basis vectors:
Substituting and simplifying yields the final projection formulas implemented in the backend:
6. Reading the chart
The direction key in the chart legend shows which way each currency's appreciation pulls the trajectory:
- Currency A — points right (0°). A rightward movement means A is gaining against B and C.
- Currency B — points upper-left (120°). An upward-left movement means B is strengthening.
- Currency C — points lower-left (240°). A downward-left movement means C is strengthening.
Arrow length reflects how fast the rates moved in that period. Distance from the origin reflects cumulative divergence from the starting configuration. The purple → blue → red colour gradient encodes time and remains readable even when arrows are not shown.
Arrows are coloured orange when a movement is statistically anomalous relative to the rest of the selected period.
Detecting strong movements
The step size for period i is the Euclidean distance between two consecutive projected points:
An arrow is coloured orange when \(d_i\) exceeds the threshold \(\tau\) defined below.
Initial approach: mean + 3σ
The first version of the threshold followed the classical z-score rule: a step was flagged as strong if it lay more than three standard deviations above the mean of all steps,
Under a normal distribution this threshold captures approximately 99.7 % of observations, leaving only the most extreme 0.3 % flagged as outliers. However, two properties of exchange rate data make this approach problematic:
- Fat tails. FX log-returns exhibit excess kurtosis: extreme moves occur far more often than a Gaussian model predicts. As a result, \(\bar{d} + 3\sigma_d\) is exceeded more frequently than the nominal 0.3 %, and the flagging threshold is less selective than intended.
- Volatility clustering and autocorrelation. Consecutive exchange rate movements are not independent — turbulent periods tend to cluster together. A global \(\sigma_d\) computed over the entire selected range can be inflated by a crisis episode, causing ordinary moves in calmer sub-periods to never be flagged, while during the crisis itself many consecutive arrows exceed the threshold at once.
- Guaranteed outliers. Because \(\sigma_d\) scales with the spread of the data, a period with uniformly large movements will still produce some flagged arrows — even though no single step is genuinely anomalous relative to its neighbours.
Revised approach: median + 3 × MAD
The current implementation replaces the mean-based rule with one built on the Median Absolute Deviation (MAD), a standard robust-statistics alternative that makes no distributional assumptions:
An arrow is coloured orange when \(d_i > \tau_{\mathrm{MAD}}\). This revision addresses all three shortcomings of the mean + 3σ approach:
- The median and MAD are both resistant to fat-tailed distributions — a small number of extreme values cannot pull the threshold upward the way a large \(\sigma_d\) can.
- Because the threshold is derived from the central tendency of the data rather than its variance, autocorrelated volatility clusters do not systematically bias the result.
- In a period where all movements are uniformly small or uniformly large, MAD approaches zero and no step exceeds \(\widetilde{d} + 4.5 \cdot \mathrm{MAD}\). Orange arrows only appear when at least one step is genuinely anomalous relative to the typical step size in the selected range.
Choosing the multiplier k = 4.5
Under a normal distribution, MAD and the standard deviation σ are related by a known scaling constant:
This allows any MAD multiplier \(k\) to be expressed as a σ-equivalent, making it directly comparable to the classical z-score rule:
The table below shows how common values of \(k\) map to their σ-equivalents and the fraction of observations they would flag under a normal distribution:
| k | σ-equivalent | Flagged under normality | Interpretation |
|---|---|---|---|
| 3.0 | ~2.0σ | ~4.6% | Elevated — too permissive |
| 4.0 | ~2.7σ | ~0.7% | Tukey mild outlier fence |
| 4.5 | ~3.0σ | ~0.3% | Classic three-sigma rule ✓ |
| 5.0 | ~3.4σ | ~0.07% | Conservative |
| 6.7 | ~4.5σ | <0.001% | Black-swan territory |
The choice \(k = 4.5\) is therefore the distribution-free equivalent of the conventional three-sigma rule: it inherits the familiar ~0.3 % nominal flagging rate of 3σ while remaining valid for the fat-tailed, autocorrelated distributions that characterise exchange rate movements.
Three separate time-series charts would show each rate individually but obscure how they move together. A single trajectory shows this directly: a sharp turn to the right means A gained on both B and C at once, a diagonal drift means the lead is split between two currencies, and so on.
7. Data source
Exchange rate data comes from the ECB Data Portal, which publishes official daily reference rates for around 40 currencies against the Euro. These are mid-point quotes with no bid-ask spread, so the no-arbitrage product u×v×w is within 0.01% of 1 on any given day. All rates are available from January 1999 onwards.
The ECB only publishes rates on trading days. Weekends and fixed ECB holidays — New Year's Day, Labour Day, Christmas, and a handful of others — are absent from the feed entirely. If a selected date boundary falls on a non-trading day, the nearest available trading day is used instead; this is expected, not a data error.
Where an individual currency series is missing an isolated trading-day entry (for example, a country-specific market closure not observed by all ECB counterparts), the last known rate is carried forward within the set of available trading days (forward-fill). No imputation is applied across weekends or holidays, since those dates are never present in the ECB feed.
8. Display and performance limits
Two automatic thresholds adapt the chart's visual features to the size of the requested dataset.
Movement arrows
Arrow rendering has three tiers. Up to 500 data points, arrows draw automatically. Between 501 and 1,500 a Show Arrows button appears below the chart — arrows are available but off by default, since drawing that many SVG elements takes a moment on older hardware. Beyond 1,500 points arrows are not generated at all; that volume of shapes would freeze most browsers. The colour gradient carries direction information in all cases.
Approximate cutoffs for the default-on tier per granularity:
- Daily — arrows on by default for ranges up to roughly 2 years
- Weekly — arrows on by default for ranges up to roughly 9 years
- Monthly — arrows always on by default (ECB history is under 320 months)
- Quarterly — arrows always on by default (ECB history is under 120 quarters)
When arrows are hidden the chart's legend shows a note to that effect.
Time-slider animation
The play/pause slider is available for every dataset size. For datasets with more than 300 data points the slider is subsampled: instead of one step per data point, the timeline is divided into at most 300 evenly spaced steps. Each step of the slider therefore advances the animation by multiple data points at once. The first and last steps always correspond exactly to the earliest and latest dates in the selected range.