Reddit stock sentiment Daniel Rotkopf

How it works

A sentiment classifier for Reddit stock discussion, and the dataset it trains on. The dataset came first, because there wasn't one.

01

Scrape

Selenium walks old.reddit search and pulls each post's comment tree.

02

Label

GPT-4o-mini reads every comment with the post it replies to.

03

Balance

Every class undersampled to the size of the smallest.

04

Train

Three LSTM variants over GPT-2 tokenised sequences.

05

Serve

This page, running the trained weights on NumPy.

Building the corpus

A Selenium scraper walks old.reddit search results for a subreddit and keyword, pages until it has enough posts, then pulls the comment tree out of each one. It keeps the post body alongside every comment, which matters later.

Labelling 45,595 comments by hand was not going to happen, so GPT-4o-mini did it — each comment passed together with the post it replies to, and asked for one of negative, neutral or positive. Without that context a comment like "this will age well" cannot be classified at all. Progress is written to disk after every batch, so a failed API call resumes instead of starting the run over.

Reddit comments skew one way, and this corpus skews negative: 20,659 negative, 16,155 neutral, 8,781 positive. Training on that as-is produces a model that says "negative" and is right half the time, so every class is undersampled down to the size of the smallest before training.

The model

The one running on this page is the first of three variants. The comment and the post go in as separate inputs, each tokenised with the GPT-2 BPE tokeniser and padded to 200. Each gets its own embedding at 128 dimensions, its own masking layer and its own LSTM of 64 units. The two final states are concatenated and put through a softmax over the three labels.

parameters~13M
sequence length200
embedding128
LSTM units64 × 2
vocabulary50,257

The other two are in the repository. The second concatenates the post and comment into one string before tokenising, so it has a single embedding and LSTM, at 500 tokens and 256 dimensions. The third keeps the two-input shape but swaps the tokeniser for one trained on this corpus rather than on the whole internet, which is worth about 18M parameters.

How well it does

Measured, not remembered: 65.0% agreement with the GPT labels across 1,200 comments drawn at random from the corpus. Always guessing the majority class would get 46.6%.

What that number is not. Those comments come from the corpus the model trained on, so it measures how well the model reproduces its labeller rather than how it does on text it has never seen. Read it as a floor — the thing is wired up correctly — not as a benchmark.

Where it goes wrong is worth knowing. It finds positive comments readily, 80% of them, but calls plenty of neutral ones positive too, so only about half of what it labels positive really is. Negative is the other way round: cautious, and usually right when it commits.

Running it here

This page does not run TensorFlow. The trained weights are read out of the Keras file into a plain array and the forward pass — two embeddings, two LSTMs, a softmax — is done in NumPy, which is small enough to sit on the same small server as the portfolio without either of them running out of memory. It answers in about 70 ms.

Live scraping is switched off in this deployment. It drives a real browser through Reddit search, which needs Chrome and a few hundred megabytes of memory, takes minutes per run, and would be an open invitation to anyone who found the URL. It still runs locally — details here.