Developing a JavaScript/TypeScript Backtesting Engine for Cryptocurrency

Historical calculations diverge from real results, and validating a strategy takes hours. We build JavaScript backtesting engines turnkey: we move calculations onto real data and cut strategy validation time many times over.

Blockchain Development Services

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1376
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1350
  • B2B Advance company logo design
    B2B Advance company logo design
    798
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1088
  • AIDER company logo development
    AIDER company logo development
    1034
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1130

Why JavaScript backtesting breaks on real data

A self-made strategy run looks convincing until the first reconciliation with a log of real trades. The discrepancy from real trades arises not because of an error in the formula, but because of assumptions that were simply not written into the code.

First — the order execution model. When a market order fills at the bar's closing price and a limit order fills on touching the level, the engine implicitly gives the strategy a better price than it would get in the order book. Slippage, partial fills, and the queue of orders at the same price level remain off-screen.

Second — data. Large history arrays in a naive implementation are loaded into memory entirely and iterated in a single loop.

Over several years of minute candles, this no longer fits in the allocated memory: the run slows down, then hangs, and results between runs become incomparable.

Third — unnoticed lookahead into the future. An indicator calculated over the entire series at once already knows subsequent quotes by the time of the signal; the same happens when recalculating on a changed data composition, on a timezone change, and on gaps in history.

Therefore, JavaScript backtesting begins not with a loop over candles, but with a description of the market model: execution rules, commissions, liquidity limits, and the order of history loading. We'll break down how this is handled in a strategy testing engine.

What a custom JavaScript backtesting engine provides

While a set of strategies is computed in an external service or by a single-threaded script, the hypothesis testing cycle stretches over hours.

An engine in JavaScript/TypeScript lives in the same runtime as the trading logic and distributes runs across the process cores: a set of dozens of configurations is computed in minutes.

The difference is not in "optimization," but in the fact that the researcher has time to test ten parameter variants before losing the thread.

Recalculation on history relies on a cache of loaded candles and incremental recalculation: when one rule is edited, only the affected sections and trades are recalculated. A full run is needed only when the dataset or execution logic changes, not on every iteration. Editing a strategy stops being an expensive operation.

Reproducibility rests on a fixed snapshot of market data, a pinned commission and slippage model, and a saved random number generator seed. The same run repeated a month later or on another machine produces a matching equity curve and a matching trade list.

Along with this, a class of failures goes away where a strategy looks viable on history only because of data discrepancies or unnoticed overfitting to the past.

The cost of calculation downtime is usually underestimated: while one variant is being computed, the queue of hypotheses stalls, and decisions are made based on an outdated picture.

When a run takes minutes, questionable ideas are filtered out on history rather than on a live account.

Approaches to calculation: event-driven, vectorized, and streaming

The choice of calculation mode determines how closely a strategy's behavior on history will match its behavior in live conditions.

Vectorized calculation processes the entire quote series in one operation: it is fast and convenient for initial hypothesis screening, but it does not reproduce the order of orders within a bar and slips past stop and target intersections.

Event-driven calculation runs data event by event — the same order as in the strategy testing mode on the live market.

The requirements for historical data differ by mode.

Vectorized needs aligned series of the same frequency without gaps; event-driven needs correct timestamps and non-synchronous sources; and streaming quote processing imposes the strictest conditions: the order and format of records must match exchange ones, including gaps and repeated publications.

Mode When it fits Limitations
Vectorized calculation Mass hypothesis screening, indicator and portfolio strategies on long history Does not model intrabar events, overstates results for strategies with stops
Event-driven calculation Orders, partial fills, working with multiple instruments at once Computes more slowly, requires complete order and commission data
Streaming quote processing Reconciliation with a live stream and replay of a recorded session Data errors pass without warning, a separate control loop is needed

In practice, the modes do not compete: we screen hypotheses with vectorized calculation, run final candidates through event-driven, and reconcile with streaming replay.

Then the discrepancy between history and reality is measured in advance rather than discovered on a live account.

Stages of developing a trading strategy testing engine

The work order is structured so that the result of each stage can be checked before moving to the next. Testing strategies on history is primarily a matter of reproducibility: the same dataset and code must produce the same report regardless of the machine and launch time. If this is not the case, any conclusions about a strategy's profitability are worthless.

  1. Audit of the calculation pipeline. We analyze how results are calculated now: where trade history is lost, whether commissions and slippage are accounted for, and whether there is lookahead into future data relative to the entry moment.
  2. Data schema design. We determine the storage format for quotes and trades, rules for reconciling different timeframes, and dataset versioning so that an old run can be repeated months later.
  3. Calculation environment setup. We fix the time source, isolate dependencies, and achieve an identical report when running locally and on a server.
  4. Core implementation. We write the quote stream handler, metric calculation, and reporting; the core remains clean, without coupling to a specific data source.
  5. Control datasets. We prepare reference runs with a known-in-advance result: a discrepancy in the number of trades or the final curve is a blocking signal, not a reason to continue.
  6. Handing the engine over to the team. We deliver configurations, a data schema description, a maintenance procedure, and the order for adding new instruments, and conduct a walkthrough with your engineers.

At the output, the team receives not a one-off calculation, but a tool that can be trusted when comparing strategies: discrepancies in reports are explained by the strategy logic, not by the behavior of the engine itself.

What remains in the repository after the engine is delivered

We deliver not "code and a call," but a working repository in which your team continues work without us. Below is what is in the branch at the time of delivery and why each artifact is needed.

  • Environment configurations — separate sets for local, test, and production loops: quote sources, parallel run limits, paths to the results storage. Secrets are pulled from environment variables and do not enter the repository.
  • Calculation data schema — entities of candles, trades, orders, and runs with field types and relationships. A new metric is integrated into it without modifying existing tables.
  • Data access policies — roles for services and people: reading history, writing results, access to trading keys. Keys are connected in read-only mode.
  • History update procedure — how missing candles are backfilled, how gaps and duplicates are checked, and in what order recalculation proceeds. An incomplete run is marked explicitly rather than passed off as a result.
  • Engine documentation — extension points with examples in JavaScript and TypeScript: how to add an indicator, order type, commission and slippage model.
  • Tests and reference runs — a fixed dataset and expected metrics that show the result has not shifted after edits.
  • Launch and rollback instructions — how to bring up the engine from scratch and return to the previous version.

We conduct the repository walkthrough at the end of the project: we go through each artifact with your team and show what to change where.

Case study: calculations got faster, discrepancies in reports disappeared

The client — a cryptocurrency quantitative trading team — calculated strategies with a set of disparate scripts: history was pulled from different sources, commissions and slippage were accounted for manually, and the monthly report reconciled with the exchange statement only after edits in a spreadsheet. One run over long history took hours, so hypotheses were tested rarely and in large batches.

We built a unified engine in JavaScript and TypeScript: the loading layer brings quotes from different sources into one schema, the calculation module reproduces the order of events within a candle, and the trade model accounts for commission, slippage, and partial fills.

We take history via CoinGecko and exchange archives, and store run results in an intermediate storage — recalculation on the same data does not start from scratch.

What changed in the measurements

  • run time over multi-year history — from hours to minutes on the same dataset;
  • share of discrepancies with real trades — from a tenth of trades to isolated cases;
  • manual report edits — from dozens of lines per report to zero.

Calculation stability was handled separately: a fixed set of history periods is run before every change, and the result is reconciled with actual trades from the execution log.

If the discrepancy exceeds the threshold, the build does not pass — a report with incorrect profitability cannot be released accidentally.

How does historical testing differ from demo trading?

Historical testing, that is, backtesting, runs the same quote stream repeatedly, so run reproducibility here is complete: identical input data produce an identical set of trades.

Hence the control of calculation conditions — commissions, funding, slippage, limit order triggering rules, and the placement moment are specified explicitly and stored together with the logic version, so strategy edits are compared over the same interval.

The flip side is the assumptions of the execution model. If an order fills at the candle's closing price, the calculation lacks the order queue, partial fills, and network latency, and the result diverges from reality more strongly the denser the trading.

Add instruments that dropped out of the sample after delisting and gaps in data — and the calculation will show robustness where there is none.

Demo trading mode provides something different: a live stream and a real execution pipeline. Here you can see exchange API request limits, order rejections, connection breaks, real spread and depth.

But repeatability is zero — the tape does not repeat, so two runs of the same logic are incomparable.

The practical conclusion: historical calculation answers whether the logic has an edge under the given assumptions, while demo mode answers whether it will withstand contact with production infrastructure.

These answers must not be mixed: strategy quality is assessed on history, while from demo you take rejection facts and behavior during failures.

Technical doubts before starting development

Doubts before starting are usually the same: the calculation is too ideal to trust, the engine will not reconcile with production code, the run will hit resource limits. Below is how each of them is addressed already during development.

How accurate are the calculations?

A candle-based run averages movement within the interval — an order may fill at a price that did not exist on the market. We calculate on an event stream in arrival order, and show the discrepancy with a detailed run in the report rather than leaving it for later.

How are commissions and slippage accounted for?

Commission is taken based on the execution fact: separately for limit and market orders. Slippage depends on order volume against liquidity — the model takes several levels from the book.

We repeat the run under worsened conditions: a strategy that holds up only on ideal execution is filtered out before launch.

Is it compatible with the current code?

The engine connects as a library, and your entry and exit logic remains primary. An adapter brings data and events to a common form, and a control run reconciles signals until the list of discrepancies is reduced to zero.

Will the calculation pipeline withstand the load?

History is split into ranges and calculated in parallel, data is cached between runs. A multi-year run across dozens of instruments fits into minutes, and it can be scheduled in a nightly window without occupying capacity for other tasks.

How do you avoid overfitting a strategy to history?

The sample is split into a tuning section and a validation section that does not participate in optimization.

Parameters are run across different market regimes, and the report includes the dispersion of the result: if the profit rests on a single lucky set, this is visible in advance.

Let's discuss the task and come back with an estimate and plan

To ensure the estimate is based on your data rather than averaged assumptions, to start it is enough to describe the task and send what has already been developed.

Then we review the materials, ask clarifying questions, and come back with a work plan broken down by stage — indicating what we do at each and what will be needed from you.

  • Task statement: which strategies we test, on which instruments and timeframes, and what we consider the result of one run.
  • Data sources: where we take price and trade history from, in what format the export is, whether there are gaps and how they are marked.
  • Execution rules: commissions, slippage, volume limits, leverage, cancellation logic, and partial order filling.
  • Output metrics: which indicators are needed and in what form to view them — table, chart, file export.
  • Environment constraints: where the engine runs, whether parallel running of a strategy set is needed, and whether browser operation is important.
  • Reference calculations: an example you trust, to compare with the result of our engine.
  • Acceptance: which scenarios are mandatory and what a correct backtesting result means to you.

The start order is as follows: a short conversation about the task with the engineer who will do it, then an estimate and plan by stage. We report on each stage in one format: what was done, which metrics confirm it, what limitations remain, and what is needed from you. We show the result on your data and your strategies, not on a synthetic example.

Describe the task in free form — we will come back with an estimate and work plan.