jatinhans.com / lab  ·  Case study · Currency forecasting

Which currency questions are actually answerable?

Ten years of daily prices for the euro, the yen and the rupee against the dollar. Three questions, the same data, every answer scored against a simple guess I named first. Two of the three were dead ends. The third fixes a number companies set every month.

Every input is lagged one trading day and matched to its question’s frequency. Models are scored only on data they never saw. Method in section 07, sources in the appendix.

Q1 Which way will the rate move next?Direction, next day, week or month No better than guessing
Q2 How rough will next week be?Roughness, the size of the moves 51-57% less error
Q3 What rate should we plan next month around?Next month’s average rate 16-28% less error

01 · The shape of the problem

The market hides which way it will move. It telegraphs how rough the ride will be.

Before building anything, ask the price history one question: does what happened over the last few days tell you anything about today? Ask it twice. Once about the direction of moves, once about their size.

Bar chart per pair: blue bars near zero for direction, orange bars clearly positive for size
How to read it. Each bar answers one question: how much does knowing what happened N days ago help? Zero means not at all. “lag” = how many days back you look, “autocorr” = the bar height.
Source: notebooks/01_direction.ipynb

The blue bars sit at zero on every pair. Yesterday’s direction tells you nothing about today’s. One bar on the rupee panel does dip below zero, since the rupee tends to give back a little the day after a move, but it is too small and too short-lived to build on. The models confirmed that later. The orange bars run clearly positive everywhere: wild days follow wild days, calm days follow calm days.

That single difference decides all three verdicts below. Q1 has no answer in this data. Q2 and Q3 do.

02 · Q3 · Planning rate

Plan next month around today’s rate, not last month’s average

Businesses need one exchange rate agreed in advance: a budgeting rate, a pricing rate, or a rate written into a contract. What that number is really guessing at is next month’s average rate. Almost nobody tests it as its own question.

We compared three ways of setting it at every month-end across 104 months, 2017 to 2026. Score it by asking how far off the guess was, on average, as a percentage of the average that actually happened. Smaller is better.

Average miss, %. 104 months per pair, lower is better

EURUSD
1.21
1.01
1.03
USDINR
0.90
0.66
0.65
USDJPY
1.51
1.13
1.14

A · last month’s average B · today’s rate C · small statistical model

Guess C, the statistical model, ties guess B on every pair. The win comes from a better starting point. The modelling adds nothing. The whole test fits in a few lines: at each month-end, record what each method would have guessed, then compare it with the average that actually happened.

for each month_end: guess_a = average(prices, last_month) # the common habit guess_b = price_today # just today's rate guess_c = ar_model(recent_moves).forecast() # continue recent behaviour actual = average(prices, next_month) # known a month later record(percent_miss(guess, actual) for each guess)

Source: scripts/14_period_average.py, condensed

The operational change

At each month-end, set the planning rate from today’s rate instead of last month’s average.

No model to deploy and no data to buy. It held on all three pairs tested.

03 · Q2 · Roughness

Size is forecastable where direction is not

“Volatility” just means how much the price jumps around. A calm week versus a wild one. Knowing next week will be wild says nothing about which way prices go, but it does tell a business how much safety margin to leave.

Two forecasters raced each other one week ahead, over 426 separate weeks per pair. The do-nothing guess assumes next week is as wild as the recent past. The volatility model is a textbook recipe called HAR. It blends yesterday’s choppiness with last week’s and last month’s, weighting each by what has worked historically.

# three views of recent choppiness ... vol_yesterday = abs(move_yesterday) vol_last_week = typical_move(last_5_days) vol_last_month = typical_move(last_21_days) # ... blended into one forecast, weights learned from history next_week_vol = w1*vol_yesterday + w2*vol_last_week + w3*vol_last_month

Source: scripts/10_turbulence_har.py, condensed

The model cuts forecast error roughly in half on every pair, 51-57% less, measured by the scoring rule statisticians use for volatility forecasts. That rule punishes under-warning about a storm harder than over-warning about one.

Line chart: realised weekly volatility in gray, the model's week-earlier forecast in blue, tracking each other
How to read it. The gray line is how wild each week of EURUSD actually turned out to be. The blue line is what the model predicted a week earlier. The forecast earns its keep because the two track each other. The vertical axis (“annualised %”) is the roughness scale: higher = wilder.
Source: scripts/10_turbulence_har.py

Next-week forecast error. 426 weeks per pair, lower is better

Pair Forecaster Error score vs do-nothing
EURUSDDo-nothing guess0.84baseline
Volatility model (HAR)0.3657% less error
HAR + machine learning0.42worse than HAR alone
USDINRDo-nothing guess1.72baseline
Volatility model (HAR)0.8451% less error
HAR + machine learning0.99worse than HAR alone
USDJPYDo-nothing guess1.13baseline
Volatility model (HAR)0.5551% less error
HAR + machine learning0.66worse than HAR alone

Error score is the volatility scoring rule described above. Units matter only for comparison. Monthly results in A.3.

Look at the third row of every block. Stacking machine learning on top of the simple recipe made forecasts worse every time at this horizon. The extra complexity cost accuracy and bought nothing.

This tells you how much cushion to leave, and nothing about which way the rate moves. In a week forecast to be wild you buy protection sooner and price in a bigger buffer. Put less at stake. In a week forecast calm, run the reverse.

04 · Q1 · Direction

Nothing beat “the next period repeats the last one”

Fifteen approaches were graded on direction: classic trading rules, statistical models, machine-learning models, and combinations of all three. Every one raced the laziest guess available. Assume the next period does what the last one did.

Accuracy is scored out of 100, so “+6.4” means the best model was right about six more times per 100 guesses than the lazy one. Every advantage here came in smaller than the uncertainty around it. Any of them could be luck.

Extra correct guesses per 100 vs lazy guessing. 322 predictions per pair, final unseen test period

Pair Extra correct Could it just be luck?
EURUSD+6.4Yes, too close to call
USDJPY+1.6Yes, too close to call
USDINR+1.4Yes, too close to call

A model can look fine on accuracy and still be dangerously overconfident

Some models state a confidence with every guess (“70% sure it goes up”). We checked whether those confidences are honest: when a model says 70%, is it right about 70% of the time?

Calibration chart: the simple model's dots near the diagonal, the complex model's far from it
How to read it. Each dot is a batch of predictions. Left to right is what the model claimed. Up and down is what actually happened. The dashed diagonal is where an honest model’s dots would sit. “held-out block” = the final stretch of data, kept hidden while the models were learning.
Source: notebooks/01_direction.ipynb

Logistic regression sits near the line, so its stated confidence means something. Gradient boosting is far off it. When it claimed “90% sure” it was right barely half the time. If you act on a stated probability, this chart matters more to you than any accuracy table.

The Fed’s words measure cleanly and add nothing

The second dead end got tested rather than assumed. All 99 FOMC statements were scored hawkish to dovish by counting phrases, a method any reader can check. The scorer passes its sanity check: the 2022-23 rate-hike era reads hawkish, the 2020 emergency cuts read dovish.

Timeline of Fed statement scores 2015-2026: dovish through 2020, strongly hawkish 2022-23
How to read it. Red dots = hawkish statements, green = dovish, gray = neutral. The black line is the average of the last four meetings.
Source: scripts/12_stance_score.py · scoring method and full numbers in A.4

Fed into the models across the same 402 weeks, the scores changed nothing. The input was rejected. The experiment stays in the repo as a documented negative, because a measurement can be valid and still add no forecasting value, and both halves of that are worth publishing.

05 · The most transferable result

When a forecast looks too good for its field, audit the timestamps first

Early on, one model looked 70% accurate at predicting the next day. Implausibly good. The cause was a timestamp mismatch: Yahoo stamps its currency prices earlier in the day than it stamps the dollar index. Join the two on the same date and “today’s” dollar move quietly carries information about tomorrow’s euro move. The model was peeking.

The check that caught it uses correlation, a score from −1 to +1. Near 0 means no connection. Near −1 or +1 means strongly connected.

eur = log(closes["EURUSD"]).diff() # the euro's daily moves dxy = log(closes["DXY"]).diff() # the dollar index's daily moves corr(dxy, eur) # -0.09 -> no connection, as expected corr(dxy, eur.shift(-1)) # -0.87 -> connected to TOMORROW's move = a leak # fix: lag the index series one day, so a model only ever sees # values that existed before the price it predicts from dxy = closes["DXY"].shift(1)

Source: notebooks/01_direction.ipynb, section 1

After the one-day shift, the 70% “accuracy” collapsed to ordinary guessing. Every number on this page comes from the fixed data. Of everything in this project, finding that bug is the part most likely to be useful to someone else.

06 · The close

What ten years of public data rules out, and what it supports

What this rules out

Rate-direction inputs

Ten years of public data give no reliable basis for a “the rate will move this way next” prediction at day, week or month horizons. Worth knowing before it reaches a roadmap.

What this supports

Roughness-aware planning

Forecast the size of the moves and let that set your buffers and your limits. It is the part of the problem public data actually answers.

What it costs to check

Free data, open tools

Every result here comes from public sources and open-source tooling, and reruns from the repository. Ruling something out this cheaply was worth the time on its own.

07 · How it was tested

Baselines named before models were built

A named “lazy guess” for every question

Repeat-the-last-period for direction, assume-it-continues for roughness, carry-forward for the planning rate. Beat the lazy guess or it doesn’t count. Half of what this study found came from fixing the comparison rather than the model.

No peeking, enforced in code

Every input is shifted one trading day. Daily data predicts the next day, weekly the next week, monthly the next month. Models slide forward through time and are scored only on data they have never seen. Mixing frequencies is where accidental peeking usually creeps in.

Uncertainty stated on every claim

Every headline number carries a statistical uncertainty range and its sample size. Where the sample is small, I call the result an anecdote.

Confidence scored, not assumed

Stated probabilities are graded against reality. Overconfident models get their confidence corrected using data they haven’t seen, or are shown as-is with a warning attached.

Fully reproducible

Data, pipeline, models and every chart on this page regenerate from the repository. Two notebooks tell the story end to end, and numbered scripts rebuild every table.

Appendix

Detail, caveats and what sits behind the summary

A.1 sources · A.2 planning-rate table · A.3 monthly roughness · A.4 Fed language · A.5 limits · A.6 repository

A.1 - Inputs, sources and coverage

Nothing here is proprietary or paid. That is deliberate: it shows what currency forecasting can deliver before anyone spends money on data. Coverage gaps are stated rather than papered over.

Input What it captures Source Coverage
Daily pricesClosing price for each pair, back to 2015 (the first year warms up the calculations)Yahoo FinanceAll 3 pairs
Recent movesThe last move plus how far the price travelled over 1, 3, 6 and 12 monthsDerivedAll 3 pairs
ChoppinessHow much the price has been jumping around, over short, medium and long windowsDerivedAll 3 pairs
Market backdropMoves in the dollar index and the VIX, a widely used market-nervousness gaugeYahoo FinanceAll 3 pairs
Interest-rate gapThe difference between what each currency earns in interestFREDAll 3 pairs
Fund positioningWhether large funds are betting for or against the euro and the yen, reported weeklyCFTCNo rupee version exists
Policy language99 statements from the Fed’s rate-setting committee, scored hawkish to dovishUS Federal ReserveDollar side only

A.2 - The planning-rate comparison in full

The three methods scored side by side. Guess C ties guess B on every pair, which is why the body section shows the comparison as bars rather than a table.

Pair A · last month’s avg B · today’s rate C · model B vs A
EURUSD1.211.011.03−16%
USDINR0.900.660.65−28%
USDJPY1.511.131.14−25%

Average miss as a % of the average that actually happened. 104 months per pair, lower is better.

A.3 - Roughness at the monthly horizon

The weekly result is the headline because it is the horizon most decisions run on. The monthly horizon has a smaller sample and a less clean story: the simple model still wins on two pairs, the machine-learning variant narrowly takes the rupee, and all the gaps are smaller.

Pair Forecaster Error score
EURUSDDo-nothing guess0.181
Volatility model (HAR)0.153
HAR + machine learning0.219
USDINRDo-nothing guess0.626
Volatility model (HAR)0.488
HAR + machine learning0.465
USDJPYDo-nothing guess0.335
Volatility model (HAR)0.259
HAR + machine learning0.370

97 months per pair, same error score as the weekly table, lower is better. One caveat on measurement: roughness is estimated from end-of-day prices. Minute-by-minute data would measure it more precisely and wasn’t available, so the claims are kept proportionate to that.

A.4 - The Fed-language method and numbers

A transparent dictionary method: count phrases, so any reader can check why a statement scored the way it did.

hawkish_phrases = ["inflation remains elevated", "tightening", ...] dovish_phrases = ["accommodative", "downside risks", ...] score = (hawkish_hits - dovish_hits) / (hawkish_hits + dovish_hits) # score runs -1 (fully dovish) .. +1 (fully hawkish)

The direction models were run with and without these inputs on the same 402 weeks. One note so the numbers don’t surprise you: these are three-way guesses, up or down or roughly flat, so random guessing scores about 33%. Numbers below 50% are normal here.

Model Without, % With, % Verdict
Logistic regression39.638.1No measurable effect
Gradient boosting42.543.8No measurable effect

Accuracy barely moved, and it moved in opposite directions for the two models. The uncertainty around both differences includes zero.

A.5 - Known limits of this study

  • -Three pairs only, two heavily traded and one from a developing economy. Don’t assume the findings carry to other currencies
  • -Roughness is estimated from end-of-day prices rather than measured minute by minute
  • -No rupee version of the fund-positioning data exists, so USDINR sits that input out
  • -Central-bank language covers the Fed only. The Indian central bank’s statements were not machine-collectable
  • -No transaction costs are modelled. This measures forecast quality, and says nothing about whether a trading strategy would clear costs

A.6 - What is in the repository

  • -Two notebooks that tell the whole story. Direction end to end, then roughness, the planning rate and the Fed-language experiment
  • -The full bench of fifteen direction models, including classic institutional and retail trading rules, each with its own scorecard
  • -All raw data committed, so everything reruns offline. An offline validator checks every stored file
  • -Every table and chart on this page, regenerable from numbered scripts