Trading indicators look brilliant on a chart you picked after the fact. The only honest way to know whether a rule works is to test it on data it has never seen, with realistic costs, and to write down what you assumed. CasaTrend is the stock backtesting platform I built for the Casablanca Stock Exchange to do that: it collects prices every evening, scans for trend signals, sends alerts, and runs reproducible research experiments on the full history.

This article covers how the system is built, step by step, and what the research found. It is an engineering case study, not investment advice.

The project at a glance

What it does Daily price collection, signal scanning and email alerts, portfolio and holdings tracking, strategy research with stored, reproducible experiments
Data 74 listed stocks (67 used in research), about 235,000 daily price rows going back to 1999
Backend Java and Spring Boot, MongoDB, scheduled jobs, Selenium with headless Firefox for collection
Security Single sign-on through Keycloak (OpenID Connect)
Deployment GitHub Actions builds a Docker image, pushes it to GitHub’s registry and deploys it over SSH to a VPS behind Traefik; the server never holds the source code

The problem: small markets, scattered data, optimistic backtests

The Casablanca market is small and its data is harder to get than for US stocks. Free sources offer charts, not clean history you can query. And most strategy tests you find online share three flaws: they ignore commissions and slippage, they tune parameters on the same data they report results on, and they never say what was assumed. I wanted a tool that fixes all three, and that runs on its own every evening without me opening a chart.

Step 1: Collect the data every evening, automatically

A scheduled job runs after the market closes, in the market’s own time zone, on weekdays:

@Scheduled(cron = "${batch.cron}", zone = "${batch.zone}")   // 0 0 18 * * MON-FRI, Africa/Casablanca
public void nightlyBatch() {
    signalBatchService.runAndWait("schedule");   // update prices, then look for new signals
}

The price source only serves its historical data to a real browser session, so the collector drives headless Firefox with Selenium and reads the data the page itself requests. That decision shaped the deployment: the Docker image contains Java and Firefox, and I measured its memory use with the real data before choosing the server limits (it peaked at 1.4 GB under a 2 GB cap).

Every batch run is recorded with its start, end and result, so a failed evening shows up in the dashboard instead of silently leaving a gap in the data. Before any research run, a data quality service checks every stock’s stored history as collected and reports which stocks the run may use and why the others are left out. It only counts; it never silently “repairs” or deletes data.

Step 2: Signals and alerts

After the prices are updated, the same batch evaluates each stock with the trend rules the user follows and sends an email when a new signal appears. The app also covers the day-to-day side: holdings and portfolio tracking, a manual trading journal, intraday bars, and notification settings per user. The point is that the platform does the routine checking, and a person only looks when something has changed.

Step 3: A research engine that can be trusted

The research engine is a pure Java component: it receives prepared price series and returns results, with no database or network calls inside. That makes it fast and easy to test. Each experiment is defined by explicit, typed settings:

public record Costs(double commissionPct, double minFee, double slippagePct) { ... }

public record Execution(double capital, double positionFraction, int maxFillGapDays,
                        double priceLimitPct, boolean fillAtClose) { ... }

public record Rules(boolean relVolume, boolean maTrend, boolean marketUp,
                    double minTradedValue, String confirm, ...) { ... }

Costs, fills and rules are part of the experiment, not hidden defaults. You cannot run a test without deciding what a trade costs, whether orders fill at the close of the signal day or at the next open, and how many days of missing data are acceptable for a fill.

Step 4: Make every experiment reproducible

Results are only worth something if you can reproduce them. Every stored experiment records its formula version and a fingerprint of the exact dataset it ran on:

formula   range-filter-close/ema-smooth-2p-1/v3-daily-or-weekly-bars
dataset   67 stocks, 64028 rows, to 2026-10-09, sha256:fe4e3f2da66b380a

If the data changes (a new month of prices, a corrected row), the fingerprint changes too, and two results are never compared as if they came from the same input.

Step 5: Train, validation and test, like a machine learning project

The history is split in time: parameters are explored on the oldest segment (to 2016), checked on a validation segment (to mid-2021), and only then reported on the most recent, untouched test segment. A whole grid of parameter settings (56 combinations) is run, not just the one that looks good, and each stock is traded with the same fixed amount so a few large positions cannot dominate.

What the research showed

The experiments tested a trend-following indicator, the Range Filter, in two setups. The two gave opposite pictures, and the difference between them was the main finding:

Stock backtesting platform results: heatmap of 56 Range Filter settings, all with a negative median on the test period
With realistic costs, none of the 56 settings had a positive median on the untouched test period.

Three more results were more useful than any single return figure:

Bar chart of the median result per stock falling from +27% with no costs to -21% with 2% commission
The same strategy goes from profitable to losing as costs rise; break-even is about 0.7% per order.

Because no candidate survived out-of-sample validation, no trading script was written for it: the brief said to build one only if the evidence supported it. The research report separates what was measured from what was assumed, and states that nothing in it is a recommendation to trade. For anyone building analytics, that separation is the most important habit to copy.

Step 6: Deployment without source code on the server

Every push to the main branch runs a pipeline:

  1. GitHub Actions builds the Docker image and stores it in GitHub’s container registry.
  2. It copies the production Compose file to the server.
  3. Over SSH, the server pulls the new image and restarts the container behind Traefik, which handles HTTPS.

The server only holds two files: the Compose file the pipeline sends and an .env written once by hand with permissions 600. Sign-in goes through Keycloak, so the app has no password storage of its own and other tools can share the same accounts.

Lessons from building a stock backtesting platform

Need a data platform like this?

The same building blocks fit far more than stock markets: collecting prices or listings from websites every day, cleaning and storing them, alerting when something changes, and giving your team dashboards and research tools they can trust. I can build the collection, the storage, the analysis and the deployment, and write down clearly what the numbers do and do not mean.

Official resources for stock backtesting platform

Documentation and references for stock backtesting platform: