ADVERSATTACK target financial sentiment classifier
fgsm → hotflip · pure numpy · financial phrasebank
Adversarial ML · Market Fragility

One Character, Opposite Signal

2026/ Python· NumPy· FGSM → HotFlip· Financial PhraseBank

I trained a sentiment classifier on financial headlines, the kind of thing that feeds automated trading signals, then went looking for the smallest change that fools it. It turns out one character does it: a typo you would read straight past flips a bullish headline to bearish. Everything below is generated by the code, nothing is faked.

Held-out test sentence 1 character edited Positive 93% → Negative
inputDiluted EPS rose to EUR3 .68 from EUR0 .50 .
attackDiluted EPS ruse to EUR3 .68 from EUR0 .50 .
Before ▲ Positive 93%
After ▼ Negative 37%
Negative Neutral Positive edit: o → u · rank #1 of 3,385
76.5%
held-out accuracy on 3 classes, a real model not a strawman
18%
of confident-positive headlines flip to negative with one character
48%
flip within two characters
#1
the gradient ranked the winning edit first, out of thousands

A confident model, undone by a typo

The sentence above is real, from the held-out split of the Financial PhraseBank (the standard benchmark for headline sentiment). The model reads it as positive with 93% confidence, which is right: diluted earnings per share rose sevenfold.

Change one letter, so rose becomes ruse (a typo you would read straight past), and the call inverts to negative. Nothing about the earnings changed. Nothing a reader would even notice changed. The number a trading system acts on just flipped sign.

This isn't a fluke on one sentence, it's a property of the model, and it's measurable. Out of 120 headlines the model confidently calls positive, roughly one in five flip to negative with a single character, and nearly half fall within two.

FGSM, and why text breaks it

The attack is the Fast Gradient Sign Method (Goodfellow et al., 2015), the standard way to fool a classifier. You take the gradient of the loss with respect to the input and step in the direction that increases it the most.

x adv = x − ε · sign( ∇x loss( x , target ) )

For images this just works, since every pixel is a real number and the nudged image is still an image. Text is discrete. You can't move the word rose by 0.007 toward "bad news", there is nothing between rose and ruse. When I run FGSM literally here it drives the target-class loss from 1.97 to ≈0, a complete fool, but the vector it lands on sits between real words and spells no actual headline.

So the gradient can't be applied, but it can still point. Its discrete descendant, HotFlip (Ebrahimi et al., 2018), scores every single edit (substitute, insert, or delete one character) with that same gradient, then verifies the best few with an exact forward pass. In the flagship attack the gradient ranked the winning edit first out of 3,385. The model is linear, so that first-order estimate is exact: the gradient doesn't approximate the best edit, it names it.

All of it is from scratch in NumPy: the TF-IDF features, the softmax classifier trained by hand, and the attack. No PyTorch, and no scikit-learn for the parts that matter. About 200 lines you can read end to end.

More one-character flips (and one that survives)

NegativeNeutralPositive
Held-out · substitution89% → flip
Operating profit imploved to EUR 20.3 mn from EUR 11.4 mn .
POS 89%NEG 50%
Held-out · substitution73% → flip
Passenger volumes rlose by 8.4 % in the accounting period .
POS 73%NEG 48%
Held-out · insertion67% → flip
Operating profit was EUR 11.07 mn , upm from EUR 8.65 mn .
POS 67%NEG 64%
Held-out · robust to 2 editsno flip
Mangins imploved as sales beat expectations
POS 95%POS 57%

Not everything is fragile. The last headline stacks three sentiment cues (improved, beat, expectations) and shrugs off two edits. The weakness concentrates where a single token carries the whole verdict, which in short headlines is most of the time.

It was never really about typos

The typo is just a symptom. The real problem is that the model decides on surface tokens, not meaning. Swap one word for a calendar month, which carries no sentiment at all, and it tips over the same way:

inputThe bank reported strong growth in net profit
attackThe bank reported strong march in net profit
Before ▲ Positive 89%
After ▼ Negative 38%

Look at what the model actually leans on. Its most negative-weighted tokens aren't words of distress, they're leftovers from how negative sentences happened to be written in the training data:

down fell decreased "3" "11" "17" by ''

A model whose idea of "bad news" includes the numeral 3 and a stray quotation mark hasn't learned sentiment. It has learned the accent of its training set, and an accent is easy to imitate or corrupt.

Why this matters for automated trading

I'm not saying AI trading is doomed. Real desks use bigger models, several signals, and risk limits. The narrower point is the one I'd actually stand behind: anything that turns text into a number inherits an attack surface, and accuracy on a clean benchmark tells you nothing about how it holds up on adversarial input.

Text sentiment is wired into real capital now. If the path from words to a trade can be flipped by edits a human can't even see, then everyone who touches the text is a potential adversary: the PR team A/B-testing a headline, a wire feed that gets compromised, a coordinated batch of typo'd posts, or a prompt slipped into the news scraper an LLM agent is reading. None of that needs a break-in. It just needs to know which word the model is standing on.

A stronger model raises the cost but doesn't change the shape. The same gradient recipe scales to transformers (TextFooler, BERT-Attack), where the edits turn into fluent synonyms instead of typos and get harder to catch, not easier. Robustness is a separate thing you have to pay for on purpose: adversarial training, knowing where your text came from, ensembling, and a human sitting between the signal and the order. What makes it worth caring about is that the fragility is cheap to show and cheap to exploit, which is usually the kind of thing that ends up mispriced.


Method & reproduction

How it works. Word and character n-gram TF-IDF features feed a softmax classifier I train by hand with Adam. The attack takes the loss gradient with respect to those features and searches single-character and single-word edits. It's NumPy-only and deterministic. Clone the repo, run python train.py then python demo.py, and every figure here regenerates on your machine.

Limits, plainly. This is a linear bag-of-features model, not a transformer. The point isn't this exact model, it's that the class of weakness is real and general, shown on something simple enough to read in full. 76.5% is an honest TF-IDF baseline for this 3-class benchmark, not a tuned number. A stronger model is harder to fool with a typo and just as foolable by a fluent paraphrase.

Data. Financial PhraseBank, from Malo, Sinha, Korhonen, Wallenius and Takala (2014), licensed CC BY-NC-SA 3.0. Downloaded on first run and never redistributed here; a few sample sentences are quoted for commentary with attribution. FGSM: Goodfellow, Shlens and Szegedy (2015). HotFlip: Ebrahimi, Rao, Lowd and Dou (2018).