Claude-geschriebener Währungsconverter fuer Trading-Dashboard liefert bei fehlendem Kurs einfach 1.0
Ein Entwickler hat eine Funktion von Claude Opus 5.5 fuer sein privates Trading-Dashboard testen lassen, die alle Positionen vor der Summierung in die Portfoliowaehrung umrechnet. Das Problem: Kennt das Modell einen Wechselkurs nicht, gibt die Funktion stillschweigend 1.0 zurueck.

On Monday I posted a function Claude Opus 5.5 wrote for my personal trading dashboard and asked whether you'd merge it. Here's the answer.
The function converts every position into my portfolio currency before the dashboard adds them up:
@cached ( ttl = 3600 ) def fx_rate ( from_ccy: str, to_ccy: str ) -> float: if not from_ccy or not to_ccy or from_ccy == to_ccy: return 1.0 # Les cotations londoniennes sont en pence (GBp) factor = 1.0 if from_ccy == " GBp ": from_ccy, factor = " GBP ", 0.01 if from_ccy == to_ccy: return factor q = quotes ([ f " { from_ccy }{ to_ccy } =X " ]) rate = next ( iter ( q. values ()), {}). get ( " price " ) return ( rate or 1.0 ) * factor Enter fullscreen mode Exit fullscreen mode It reads well. It knows that London stocks are quoted in pence. It caches rates so I don't hit the API limits. It short-circuits when there's nothing to convert.
When the quote provider fails or doesn't know the symbol, quotes() returns an empty dict. Follow it through:
q = {} rate = next ( iter ({}. values ()), {}). get ( " price " ) # → None return ( None or 1.0 ) * 1.0 # → 1.0 Enter fullscreen mode Exit fullscreen mode No exception. No log. The function answers with a perfectly plausible number.
Line 3 does the same thing in a different place: if a position has no currency, it's treated as if it were already in the target currency.
And then @cached(ttl=3600) makes it worse. If your decorator stores whatever comes back — and most do — that 1.0 stays there for an hour. A quote provider that hiccups for one second gives you sixty minutes of wrong valuations.
Say I hold ¥1,000,000 of a Japanese stock, and my dashboard reports in dollars.
correct: 1,000,000 JPY × 0.0067 ≈ $6,700 fallback: 1,000,000 JPY × 1.0 = $1,000,000 Enter fullscreen mode Exit fullscreen mode The position appears about 150 times larger than it is, inside a total that still looks like a real number. On a page whose job is to help me decide what to buy and sell, that's the worst kind of failure: silent, confident, and wrong.
The whole project started from this prompt (my translation; I wrote it in French):
I want to build a platform to organise and plan my trading. I need to know about the news instantly, see what I can invest in, see the confidence index, how many people bought shares in it, the returns, etc. Plan what you need to set up for this.
It says nothing about what to do when data is missing. So the model filled that silence with the most comfortable choice for the code itself: never crash, always return something.
That's the part I keep coming back to. The missing requirement — in a trading tool, a wrong number is worse than no number — is obvious once you say it. I never said it. A better model won't fix that; it will follow my incomplete prompt even more faithfully.
Fail loudly, and let nothing wrong reach the cache. This is the version I'm moving to:
SUBUNITS = { " GBp ": ( " GBP ", 0.01 ), " ZAc ": ( " ZAR ", 0.01 ), " ILA ": ( " ILS ", 0.01 )} @cached ( ttl = 3600 ) def fx_rate ( from_ccy: str, to_ccy: str ) -> float: if not from_ccy or not to_ccy: raise ValueError ( " Missing currency " ) from_ccy, f_from = SUBUNITS. get ( from_ccy, ( from_ccy, 1.0 )) to_ccy, f_to = SUBUNITS. get ( to_ccy, ( to_ccy, 1.0 )) if from_ccy == to_ccy: return f_from / f_to q = quotes ([ f " { from_ccy }{ to_ccy } =X " ]) rate = next ( iter ( q. values ()), {}). get ( " price " ) if rate is None: raise LookupError ( f " No quote for { from_ccy } / { to_ccy } " ) return rate * f_from / f_to Enter fullscreen mode Exit fullscreen mode Caching decorators generally store return values, not exceptions, so a failed lookup is retried on the next call instead of being frozen for an hour. It's worth checking that yours behaves that way.
The caller changes too: when a rate is unavailable, the dashboard shows "rate unavailable" for that position and marks the total as incomplete. It never adds up a total that's quietly wrong.
Pence only worked in one direction. The original handled GBp as a source currency, but fx_rate("USD", "GBp") returned the USD→GBP rate, 100 times too small. Johannesburg ( ZAc ) and Tel Aviv ( ILA ) also quote in sub-units; for those, the symbol most likely doesn't exist, the quote comes back empty, and you're back to 1.0. The fix handles both directions and all three.
"The factor should be 100, not 0.01." It shouldn't. A penny is 0.01 pounds; converting from pence means multiplying by 0.01, then by the pound's rate.
"The second if from_ccy == to_ccy is dead code." It isn't. After GBp becomes GBP, it correctly handles fx_rate("GBp", "GBP"), which should return 0.01.
"Use Decimal instead of float." Reasonable for accounting. For a displayed exchange rate, it doesn't change the outcome.
"A one-hour cache means stale rates." That's what a TTL is for. The problem isn't stale rates, it's invented ones.
Every line here is defensible on its own. The bug is a default: a value the code reaches for when it doesn't know, chosen to keep things running rather than to keep them true.
Worth searching your own code for: or 1.0, or 0,.get(key, 0), an except that returns something plausible. Each one is a question about what your program should do when it doesn't know — and whether anyone ever answered it.
I asked Claude Code to search the rest of my project for the same pattern. That list will probably be next week's article.
I write one of these every week from code an agent wrote for my own projects. If you'd rather catch the next one before the answer: https://discord.com/invite/WsYyZk5sWj