Building a Real-Time Price Anomaly Detector with Python, SerpApi, and Robust Statistics
Modern price monitoring systems need to do more than tell you that a price changed. A single abnormal listing, a scraped error, or a temporary outlier can make a traditional threshold-based detector fire an alert when nothing meaningful happened. In this project, I built a lightweight real-time price anomaly detector in Python that combines: A rolling median baseline Median Absolute Deviation (MAD) Robust Z-scores Short-term percentage returns Trend confirmation Alert cooldowns The goal is simple: detect meaningful price movements without overreacting to noisy observations. Note: This project monitors retail prices from Google Shopping results through SerpApi. It is a retail-price monitoring example, not a financial exchange-data feed. What we're building The pipeline looks like this: The...
Modern price monitoring systems need to do more than tell you that a price changed. A single abnormal listing, a scraped error, or a temporary outlier can make a traditional threshold-based detector fire an alert when nothing meaningful happened. In this project, I built a lightweight real-time price anomaly detector in Python that combines: A rolling median baseline Median Absolute Deviation (MAD) Robust Z-scores Short-term percentage returns Trend confirmation Alert cooldowns The goal is simple: detect meaningful price movements without overreacting to noisy observations. Note: This project monitors retail prices from Google Shopping results through SerpApi. It is a retail-price monitoring example, not a financial exchange-data feed. What we're building The pipeline looks like this: The implementation is intentionally small and interpretable. The complete engine is built around a single class and a compact data structure. Why not just use standard deviation? A common first implementation is: The problem is that standard deviation is sensitive to extreme observations. Suppose your historical prices are: Then one bad observation such as: can distort the mean and standard deviation. That can move your detection boundary away from the actual market behavior you are trying to model. For a noisy retail environment, a more robust baseline is useful. That's where median and Median Absolute Deviation come in. 1. Building a rolling median baseline Instead of storing an unlimited stream of prices, the engine keeps a bounded history using Python's : The default window size is , so the detector always works from a recent local history rather than an ever-growing dataset. The baseline is then: Why use a median? Because the median is much less affected by one unusually high or low observation. For example: Add an extreme outlier: The median remains: That makes it a useful local reference point for anomaly detection. 2. Median Absolute Deviation (MAD) The next step is measuring how far observations normally vary around the median. MAD is calculated as: The implementation is deliberately straightforward: The important idea is that we measure the absolute distance from the median and then take the median of those distances. This makes MAD much less sensitive to extreme observations than standard deviation. 3. Turning MAD into a robust Z-score Now we can calculate a robust version of the familiar Z-score: In Python: The implementation also handles two important edge cases: That prevents the detector from making a decision before enough history exists and avoids division by zero when historical prices are identical. Why 0.6745? The constant scales MAD so the resulting score is approximately comparable to the familiar standard-normal Z-score. This lets us use an intuitive threshold such as: without requiring the model to be trained. 4. Detecting short-term price movement A large deviation from the baseline is useful, but it does not tell us everything. The detector also looks at the most recent price-to-price return: For example: That helps distinguish a statistically unusual price from a very small movement that happens to have a relatively large standardized score. The configured default return threshold is: which corresponds to a 3% change. 5. Confirming the trend A single spike is different from a sustained move. The detector therefore checks the most recent observations: Then it checks whether the recent prices are consistently rising: and whether they are consistently falling: This gives us an additional signal: The implementation assigns higher severity to confirmed rising or falling anomalies. 6. Combining the signals The detector starts from a safe default: Then it requires both a robust statistical signal and a short-term return threshold. Positive anomaly: Negative anomaly: For positive movement: For negative movement: This is an important design choice. We are not saying: "The Z-score is high, therefore alert." We are saying: "The price is statistically unusual, the recent movement is large enough, and the short-term context tells us what kind of anomaly it is." The complete evaluation logic follows this sequence and only appends the new observation after evaluating it against the previous history. Getting real retail prices with SerpApi The project uses SerpApi to query Google Shopping results. The request parameters are: Instead of blindly trusting the first listing, the engine checks up to the first ten shopping results: It extracts numeric values, ignores unusable observations, and then calculates the median of the usable prices: That final median becomes the market observation used by the anomaly detector. This extra median step is useful because a single seller listing should not automatically become the entire market price signal. The result object Each evaluation returns a structured result: That gives downstream code everything it needs to display, log, store, or route the event elsewhere. For example: Avoiding alert spam A detector that sends the same alert every polling cycle becomes annoying very quickly. The engine therefore implements a simple cooldown: With a cooldown of five minutes: the system can recognize repeated anomalies without emitting an alert every few seconds. This separates two ideas that are easy to confuse: The detector may identify an anomaly while the notification layer decides whether it is time to emit another alert. Running the monitor The monitor is implemented as a polling loop: The loop also supports a controlled value, which is useful for local verification and testing. Complete minimal setup save this code snippet as price_alert_engine.py create the requirements.txt file and include Install SerpApi's Python package: Set your API key: Then run: The example configuration monitors an with: and performs five cycles for local verification. Example execution flow A typical run starts by collecting enough observations to initialize the statistical baseline. During initialization, the detector returns: Once there is enough history, each new observation is evaluated against the existing price window. A simplified flow looks like: Important engineering lessons 1. Robust statistics are useful for messy data Real-world retail data is not perfectly clean. Scraping errors, unusual sellers, temporary discounts, and extreme listings can create observations that should not dominate the baseline. Median and MAD give us a simple way to reduce the influence of those observations. 2. One metric is rarely enough The engine combines: Each component answers a different question. 3. Detection and alerting should be separate A monitoring system should be able to detect repeatedly without necessarily notifying repeatedly. That separation makes the system much easier to operate. 4. Start interpretable before reaching for complex models This detector does not require a neural network or a large training dataset. Every alert can be explained: That is valuable when you need to debug false positives. Where this project can go next This implementation is intentionally lightweight, but it provides a foundation for a more complete monitoring platform. Possible extensions include: Other useful improvements would be: Per-product thresholds Seller-level filtering Persistent historical storage Structured JSON logging Retry and backoff policies Multi-market / multi-currency support A web dashboard More sophisticated change-point detection Evaluation against labeled historical anomalies At that point, the detector becomes more than a script: it becomes a small observability service for retail pricing. Final thoughts The interesting part of this project is not the amount of code. It is the decision to make the detector robust, explainable, and resistant to noisy observations. By combining a rolling median, MAD, robust Z-scores, percentage returns, trend confirmation, and cooldown-based alerting, we get a practical monitoring pipeline without introducing unn